• Stars
    star
    194
  • Rank 192,952 (Top 4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 8 years ago
  • Updated about 1 year ago

Reviews

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

Repository Details

Meteor methods with better scoping, argument checking, and good defaults.

mdg:validated-method

Define Meteor methods in a structured way, with mixins

// Method definition
const method = new ValidatedMethod({
  name, // DDP method name
  mixins, // Method extensions
  validate, // argument validation
  applyOptions, // options passed to Meteor.apply
  run // Method body
});

// Method call
method.call({ arg1, arg2 });

// Method callAsync added in 1.3.0
method.callAsync({ arg1, arg2 }); 
//      Λ†Λ†Λ†Λ†Λ† not callback based, returns a Promise.

This is a simple wrapper package for Meteor.methods. The need for such a package came when the Meteor Guide was being written and we realized there was a lot of best-practices boilerplate around methods that could be easily abstracted away.

Note: the code samples in this README use the Meteor 1.3 import/export syntax, but this package works great in Meteor 1.2 as well. In that case, we recommend attaching your ValidatedMethod objects to the relevant collection, like Lists.methods.insert = new ValidatedMethod(...).

Benefits of ValidatedMethod

  1. Have an object that represents your method. Refer to it through JavaScript scope rather than by a magic string name.
  2. Built-in validation of arguments through aldeed:simple-schema, or roll your own argument validation.
  3. Easily call your method from tests or server-side code, passing in any user ID you want. No need for two-tiered methods anymore!
  4. Throw errors from the client-side method simulation to prevent execution of the server-side method - this means you can do complex client-side validation in the body on the client, and not waste server-side resources.
  5. Get the return value of the stub by default, to take advantage of consistent ID generation. This way you can implement a custom insert method with optimistic UI.
  6. Install Method extensions via mixins.

See extensive code samples in the Todos example app.

Defining a method

Using SimpleSchema

Let's examine a method from the new Todos example app which makes a list private and takes the listId as an argument. The method also does permissions checks based on the currently logged-in user. Note this code uses new ES2015 JavaScript syntax features.

// Export your method from this module
export const makePrivate = new ValidatedMethod({
  // The name of the method, sent over the wire. Same as the key provided
  // when calling Meteor.methods
  name: 'Lists.methods.makePrivate',

  // Validation function for the arguments. Only keyword arguments are accepted,
  // so the arguments are an object rather than an array. The SimpleSchema validator
  // throws a ValidationError from the mdg:validation-error package if the args don't
  // match the schema
  validate: new SimpleSchema({
    listId: { type: String }
  }).validator(),

  // This is optional, but you can use this to pass options into Meteor.apply every
  // time this method is called.  This can be used, for instance, to ask meteor not
  // to retry this method if it fails.
  applyOptions: {
    noRetry: true,
  },

  // This is the body of the method. Use ES2015 object destructuring to get
  // the keyword arguments
  run({ listId }) {
    // `this` is the same method invocation object you normally get inside
    // Meteor.methods
    if (!this.userId) {
      // Throw errors with a specific error code
      throw new Meteor.Error('Lists.methods.makePrivate.notLoggedIn',
        'Must be logged in to make private lists.');
    }

    const list = Lists.findOne(listId);

    if (list.isLastPublicList()) {
      throw new Meteor.Error('Lists.methods.makePrivate.lastPublicList',
        'Cannot make the last public list private.');
    }

    Lists.update(listId, {
      $set: { userId: this.userId }
    });

    Lists.userIdDenormalizer.set(listId, this.userId);
  }
});

The validator function called in the example requires SimpleSchema version 1.4+.

Be aware that by default the validator function does not clean the method parameters before checking them. This behavior differs from that of aldeed:collection2, which always cleans the input data before inserts, updates, or upserts.

If you want the validator to clean its inputs before checking, make sure to pass the { clean: true } option to the validator function:

  validate: new SimpleSchema({
    listId: { type: String }
  }).validator({ clean: true }),

Using your own argument validation function

If aldeed:simple-schema doesn't work for your validation needs, just define a custom validate method that throws a ValidationError instead:

const method = new ValidatedMethod({
  name: 'methodName',

  validate({ myArgument }) {
    const errors = [];

    if (myArgument % 2 !== 0) {
      errors.push({
        name: 'myArgument',
        type: 'not-even',
        details: {
          value: myArgument
        }
      });
    }

    if (errors.length) {
      throw new ValidationError(errors);
    }
  },

  // ...
});

Using check to validate arguments

You can use check in your validate function if you don't want to pass ValidationError objects to the client, like so:

const method = new ValidatedMethod({
  name: 'methodName',

  validate(args) {
    check(args, {
      myArgument: String
    });
  },

  // ...
});

Skipping argument validation

If your method does not need argument validation, perhaps because it does not take any arguments, you can use validate: null to skip argument validation.

Defining a method on a non-default connection

You can define a method on a non-default DDP connection by passing an extra connection option to the constructor.

Options to Meteor.apply

The validated method, when called, executes itself via Meteor.apply. The apply method also takes a few options which can be used to alter the way Meteor handles the method. If you want to use those options you can supply them to the validated method when it is created, using the applyOptions member. Pass it an object that will be used with Meteor.apply.

By default, ValidatedMethod uses the following options:

{
  // Make it possible to get the ID of an inserted item
  returnStubValue: true,

  // Don't call the server method if the client stub throws an error, so that we don't end
  // up doing validations twice
  throwStubExceptions: true,
};

Other options you might be interested in passing are:

  • noRetry: true This will stop the method from retrying if your client disconnects and reconnects.
  • onResultReceived: (result) => { ... } A callback to call when the return value is sent from the server. This actually happens before the regular Method callback fires, you can read more details about the Method lifecycle in the Meteor Guide.

Secret server code

If you want to keep some of your method code secret on the server, check out Served Files from the Meteor Guide.

Using a ValidatedMethod

method#call(args: Object)

Call a method like so:

import {
  makePrivate,
} from '/imports/api/lists/methods';

makePrivate.call({
  listId: list._id
}, (err, res) => {
  if (err) {
    handleError(err.error);
  }

  doSomethingWithResult(res);
});

The return value of the server-side method is available as the second argument of the method callback.

method#_execute(context: Object, args: Object)

Call this from your test code to simulate calling a method on behalf of a particular user:

it('only makes the list public if you made it private', () => {
  // Set up method arguments and context
  const context = { userId };
  const args = { listId };

  makePrivate._execute(context, args);

  const otherUserContext = { userId: Random.id() };

  assert.throws(() => {
    makePublic._execute(otherUserContext, args);
  }, Meteor.Error, /Lists.methods.makePublic.accessDenied/);

  // Make sure things are still private
  assertListAndTodoArePrivate();
});

Mixins

Every ValidatedMethod can optionally take an array of mixins. A mixin is simply a function that takes the options argument from the constructor, and returns a new object of options. For example, a mixin that enables a schema property and fills in validate for you would look like this:

function schemaMixin(methodOptions) {
  methodOptions.validate = methodOptions.schema.validator();
  return methodOptions;
}

Then, you could use it like this:

const methodWithSchemaMixin = new ValidatedMethod({
  name: 'methodWithSchemaMixin',
  mixins: [schemaMixin],
  schema: new SimpleSchema({
    int: { type: Number },
    string: { type: String },
  }),
  run() {
    return 'result';
  }
});

Community mixins

If you write a helpful ValidatedMethod mixin, please file an issue or PR so that it can be listed here!

Ideas

  • It could be nice to have a SimpleSchema mixin which just lets you specify a schema option rather than having to pass a validator function into the validate option. This would enable the below.
  • With a little bit of work, this package could be improved to allow easily generating a form from a method, based on the schema of the arguments it takes. We just need a way of specifying some of the arguments programmatically - for example, if you want to make a form to add a comment to a post, you need to pass the post ID somehow - you don't want to just have a text field called "Post ID".

Discussion and in-depth info

Validation and throwStubExceptions

By default, using Meteor.call to call a Meteor method invokes the client-side simulation and the server-side implementation. If the simulation fails or throws an error, the server-side implementation happens anyway. However, we believe that it is likely that an error in the simulation is a good indicator that an error will happen on the server as well. For example, if there is a validation error in the arguments, or the user doesn't have adequate permissions to call that method, it's often easy to identify that ahead of time on the client.

If you already know the method will fail, why call it on the server at all? That's why this package turns on a hidden option to Meteor.apply called throwStubExceptions.

With this option enabled, an error thrown by the client simulation will stop the server-side method from being called at all.

Watch out - while this behavior is good for conserving server resources in the case where you know the call will fail, you need to make sure the simulation doesn't throw errors in the case where the server call would have succeeded. This means that if you have some permission logic that relies on data only available on the server, you should wrap it in an if (!this.isSimulation) { ... } statement.

ID generation and returnStubValue

One big benefit of the built-in client-side Collection#insert call is that you can get the ID of the newly inserted document on the client right away. This is sometimes listed as a benefit of using allow/deny over custom defined methods. Not anymore!

For a while now, Meteor has had a hard-to-find option to Meteor.apply called returnStubValue. This lets you return a value from a client-side simulation, and use that value immediately on the client. Also, Meteor goes to great lengths to make sure that ID generation on the client and server is consistent. Now, it's easy to take advantage of this feature since this package enables returnStubValue by default.

Here's an example of how you could implement a custom insert method, taken from the Todos example app we are working on for the Meteor Guide:

const insert = new ValidatedMethod({
  name: 'Lists.methods.insert',
  validate: new SimpleSchema({}).validator(),
  run() {
    return Lists.insert({});
  }
});

You can get the ID generated by insert by reading the return value of call:

import {
  insert,
} from '/imports/api/lists/methods';

// The return value of the stub is an ID generated on the client
const listId = insert.call((err) => {
  if (err) {
    // At this point, we have already redirected to the new list page, but
    // for some reason the list didn't get created. This should almost never
    // happen, but it's good to handle it anyway.
    FlowRouter.go('home');
    alert('Could not create list.');
  }
});

FlowRouter.go('listsShow', { _id: listId });

Running tests

meteor test-packages --driver-package practicalmeteor:mocha ./

More Repositories

1

meteor

Meteor, the JavaScript App Platform
JavaScript
44,041
star
2

guide

πŸ“– Articles about Meteor best practices
HTML
841
star
3

react-packages

Meteor packages for a great React developer experience
JavaScript
571
star
4

todos

The example app "Todos", written following the Meteor Guide
JavaScript
535
star
5

blaze

πŸ”₯ Meteor Blaze is a powerful library for creating live-updating user interfaces
JavaScript
526
star
6

mobile-packages

Meteor packages that provide functionality on mobile and desktop via Cordova plugins.
JavaScript
339
star
7

chromatic

Chromatic component explorer
JavaScript
280
star
8

simple-todos

The Meteor Tutorial "simple-todos" app, with one commit per tutorial step so that you can follow along.
JavaScript
243
star
9

simple-todos-react

A repository that follows the React tutorial step-by-step
JavaScript
200
star
10

docs

The Meteor API documentation.
JavaScript
186
star
11

tutorials

The Meteor tutorials from meteor.com
JavaScript
182
star
12

postgres-packages

Early preview of PostgreSQL support for Meteor (deprecated, here for historical reasons)
JavaScript
159
star
13

redis-livedata

Realtime data-sync support for Redis in Meteor
JavaScript
147
star
14

logic-solver

JavaScript
143
star
15

examples

Project examples with Meteor
JavaScript
124
star
16

eslint-plugin-meteor

🧐 Meteor specific linting rules for ESLint
JavaScript
118
star
17

meteor-feature-requests

A tracker for Meteor issues that are requests for new functionality, not bugs.
90
star
18

miniredis

javascript in-memory implementation of Redis API with observe changes interface and reactivity
JavaScript
67
star
19

cordova-plugin-meteor-webapp

Cordova plugin that serves a Meteor web app through a local server and implements hot code push
JavaScript
66
star
20

promise

ES6 Promise polyfill with Fiber support
JavaScript
63
star
21

meteor-theme-hexo

The framework we use for docs.
Less
63
star
22

madewith

JavaScript
55
star
23

simple-todos-angular

The Meteor Tutorial "simple-todos" app, the angular-meteor version, with one commit per tutorial step so that you can follow along.
JavaScript
49
star
24

babel

Babel wrapper package for use with Meteor
JavaScript
46
star
25

localmarket

Localmarket example application
JavaScript
45
star
26

react-tutorial

React Tutorial is the best place to learn how to use React and Meteor together
JavaScript
37
star
27

tutorial-tools

Work in progress: Tools to make a great step-by-step code tutorial with Meteor
JavaScript
34
star
28

windows-preview

The place to report issues with the previews of Meteor on Windows
33
star
29

module-todos-react

The todos app, built with Meteor 1.3 + React
JavaScript
32
star
30

solomo

The demo we showed at Meteor Devshop August 2014 of Meteor Mobile integration. This code has moved to the examples folder in meteor/mobile-packages.
CSS
30
star
31

redis-example

A Meteor app example that uses Redis bindings
JavaScript
28
star
32

e2e

End-to-end tests for Meteor's release process
JavaScript
22
star
33

meteor-panel

Meteor Dev Tools for Chrome
JavaScript
21
star
34

galaxy-images

Dockerfiles for Galaxy app base images
Shell
20
star
35

worldwide-meteor-day

The entire international Meteor community is hosting simultaneous meetups on November 6, open to anyone who is curious to know more about the Meteor platform.
CSS
20
star
36

node-stubs

Stub implementations of Node built-in modules, a la Browserify
JavaScript
18
star
37

chat-tutorial

Five part tutorial to build a single-room chat app.
18
star
38

tutorial-viewer

Read the Meteor tutorials in a minimalist app
JavaScript
17
star
39

accounts

🚨🚫 [wip/stalled] Project to de-couple the accounts-model. Not the official Meteor "accounts"! 🚫🚨
16
star
40

telephone-pictionary

Telephone-Pictionary crash course for Meteor
JavaScript
16
star
41

galaxy-seo-package

Wrapper around third-party Prerender.io package.
JavaScript
15
star
42

vue-tutorial

Vue
14
star
43

madewith2

a better madewith
JavaScript
14
star
44

intermediate-example

This is a step-by-step tutorial for Meteor.com of the Level Up Tutorials Intermediate Meteor series recipe app
CSS
13
star
45

ddp-graceful-shutdown

JavaScript
13
star
46

validation-error

Standardized validation error format for Meteor
JavaScript
12
star
47

com.meteor.cordova-update

Objective-C
12
star
48

galaxy-docs

Galaxy documentation
Less
12
star
49

babel-preset-meteor

Babel preset for ES2015+ features supported by Meteor
JavaScript
11
star
50

minimal-meteor-app

Minimal app for testing the Meteor 1.7 modern/legacy system
JavaScript
11
star
51

meteor-chocolatey-installer

The source of the Meteor Chocolatey Installer β˜„οΈπŸ«πŸ’»
PowerShell
10
star
52

standalone-blaze-generator

JavaScript
10
star
53

simple-todos-svelte

JavaScript
10
star
54

mongodb-builder

Docker image for building MongoDB for Linux from source.
Shell
9
star
55

demo-starter

Instructions and code to do a Meteor live coding demo.
CSS
9
star
56

blaze-tutorial

JavaScript
9
star
57

git-patch-parser

An NPM module for parsing Git patch files
JavaScript
8
star
58

devshopquestions

the new Devshop Questions app
JavaScript
8
star
59

galaxy-roadmap

7
star
60

simple-todos-vue

JavaScript
7
star
61

vue3-tutorial

Learn how to build a fullstack Vue3 app using Meteor β˜„οΈ
JavaScript
7
star
62

proposal-referential-destructuring

ECMAScript proposal to allow destructured variables that refer to object properties
7
star
63

sudoku-demo

Demo of Logic Solver
JavaScript
7
star
64

datadog-oom-journald

Datadog custom check to publish a count of Linux oom-killer actions, for machines with systemd-journald
Python
7
star
65

rectangles

Customizable dashboard app
JavaScript
7
star
66

eslint-config-meteor

The ESLint configuration recommended by the Meteor Guide http://guide.meteor.com/
JavaScript
7
star
67

piechart-demo

Demo of a basic chart
JavaScript
6
star
68

leaderboard

CSS
5
star
69

procmon

A Go library to send resource consumption data to datadog
Go
5
star
70

community

A meta-repo to talk about Meteor contributions
5
star
71

svelte-tutorial

Svelte
5
star
72

flowbite-meteor-starter

JavaScript
5
star
73

externalify

Load external modules from different Browserify bundles, without creating a global `require`
JavaScript
5
star
74

cordova-lib-npm

JavaScript
4
star
75

babel-demo

App showing Babel (ES6) examples with a "try it" box
CSS
4
star
76

theme-example

JavaScript
4
star
77

interns

A page to recognize the work of Meteor interns!
HTML
4
star
78

intermediate-tutorial

JavaScript
4
star
79

meteor-url-shortener

MDG Internal URL Shortener
JavaScript
3
star
80

meteor-test-executable

an npm module used by meteor self-test
JavaScript
3
star
81

ecmascript-runtime

Polyfills for new ECMAScript 2015 APIs like Map and Set
JavaScript
3
star
82

meteor-chrome-ext

Chrome extension used along with Meteor Url Shortener
JavaScript
3
star
83

node-aes-gcm

Meteor wrapper for the node-aes-gcm npm package
JavaScript
3
star
84

pbsolver

Pseudo-boolean solver using MiniSat+
JavaScript
3
star
85

meteor-hexo-config

The Meteor-centric Hexo configuration npm for https://github.com/meteor/hexo-theme-meteor.
JavaScript
2
star
86

circleci

Dockerfile
2
star
87

clock

Clock example application
HTML
2
star
88

armor64

a better alternative to base64
HTML
2
star
89

meteor-profiler

JavaScript
2
star
90

angular-tutorial

JavaScript
2
star
91

recurly

JavaScript
2
star
92

renovate-config-mdg-docs

Renovate configuration shared by all Meteor documentation repositories.
2
star
93

render-bench

Apps for testing and profiling rendering performance
JavaScript
2
star
94

lint

JavaScript
2
star
95

react-research

JavaScript
2
star
96

hexo-s3-deploy

JavaScript
2
star
97

js-bson

JavaScript
2
star
98

meteor_kexec

Meteor wrapper for npm `kexec` package
JavaScript
2
star
99

renovate-config-meteor-docs

2
star
100

minisat-package

Meteor package for emscriptened minisat
JavaScript
2
star