Creating Meteor Packages — capsulecat:commands

This article was originally published on January 21, 2016 on The Capsule Cat Blog. For archive purposes, we moved it here!


Every package begins with a goal, and capsulecat:commands is no different. The idea is to be able to use the Command Pattern in Meteor so that we can write reusable actions that we can dispatch synchronously, or asynchronously.

Meteor Packages

What is a Meteor Package? Very simply, it is a collection of files and assets that is reusable and can be added to a Meteor project. When you create a Meteor package, you specify its dependencies and what files it will include. Then, you publish the package to Atmosphere. Once published, any developer can add the package to their project using meteor add user:package-name.

Command Pattern

Why would we want to use the Command Pattern in Meteor? The command pattern is a great way to encapsulate actions into an object. When working with Meteor (or almost any programming framework), you will find that you will want to do the same action over and over, but with some minor changes in the request or response. The Command Pattern is a nice way of encapsulating all the data and logic necessary for an action.

The capsulecat:commands package will take the following approach to the command pattern. We will have a Command class with a handle method. When we want to make a command, we will create the class, and when we want to execute the command, we will call handle. This creation and execution will be done for us by an Invoker, which we have decided to use two functions for: dispatch and dispatchAsync.

Here’s some real code that we want to be able to write so that it is more relate-able:

class EmailNotificationCommand {
constructor(email) {
this.email = email;
},
handle() {
// Do something with this.email
console.log(this.email);
}
}
dispatchAsync(EmailNotificationCommand, 'test@test.com');

Getting Started

Every project I begin starts with me creating a Github repo. I love using Git and it saves me from a lot of heartache when I find that deleted a file, something I’m trying to do just isn’t working, or I need to keep track of Issues for the future.

Let’s start by making the package:

git clone git@github.com:CapsuleCat/MeteorCommands.git
cd MeteorCommands
meteor create --package capsulecat:commands

Alright, the first step is to edit the package.js file:

Package.describe({
name: 'capsulecat:commands',
version: '0.0.1',
summary: 'Dispatch commands synchronously and asynchronously',
git: 'https://github.com/CapsuleCat/MeteorCommands',
documentation: 'README.md'
});
function includeFiles(api) {
api.addFiles('src/commands.js');
}
Package.onUse(function(api) {
api.versionsFrom('1.2.1');
api.use('ecmascript');
includeFiles(api);
api.export(['dispatch', 'dispatchAsync']);
});
Package.onTest(function(api) {
//
});

Then we will make a folder called src and add dispatch.js to it:

dispatch = function (commandClass) {
var args = Array.prototype.slice.call(arguments);
args = args.slice(1);
var command = new commandClass(...args);
return command.handle();
};
dispatchAsync = function (commandClass) {
var args = Array.prototype.slice.call(arguments);
args = args.slice(1);
callback = null;
if (typeof args[args.length - 1] === 'function')
callback = args.slice(args.length - 1, 1)[0];
setTimeout( function () {
var command = (new commandClass(...args));
var result = command.handle();
if (callback)
callback(result);
}, 0);
};

Commit and push!

Is that really it? How do we know it works?

Testing

When you write a Meteor package, you can use TinyTest for your test, but we are going to use Jasmine for our package:

Package.onTest(function(api) {
api.use('sanjo:jasmine');
includeFiles(api);
api.addFiles('tests/dispatch-spec.js', ['server', 'client']);
});

Alright, let’s write our test in test/dispatch-spec.js:

class TestCommand{
constructor(s) {
this.s = s;
},
handle() {
return s + '-out';
}
}
describe('dispatch', function () {
it('can dispatch a command', function () {
var out = dispatch(TestCommand, 'in');
expect(out).toBe('in-out');
});
});

Alright, let’s run our test:

VELOCITY_TEST_PACKAGES=1 meteor test-packages --driver-package velocity:html-reporter --velocity ./

Deploy

The last step is to deploy our package to Atmosphere!

meteor publish --create

Thanks for reading! If you liked this article, feel free to follow us on Twitter.