quave:collections
quave:collections
is a Meteor package that allows you to create your collections in a standard way.
Features
- Schemas
- Helpers
- Composers
Compatible with Meteor 3.0.3+
Why
Every application that connects to databases usually need the following features:
- A way to access object instances when they come from the database: helpers
- Provide new methods to collections: collection
- Valid the data before persisting: schemas
- Centralize behaviors: composers
Meteor has packages for almost all these use cases but it's not easy to find the best in each case and also how to use them together, that is why we have created this package.
We offer here a standard way for you to create your collections by configuring all these features in a function call createCollection
using a bunch of options in a declarative way and without using Javascript classes.
We also allow you to extend your Meteor.users
collection in the same way as any other collection.
We believe we are not reinventing the wheel in this package but what we are doing is like putting together the wheels in the vehicle :).
Installation
meteor add quave:collections meteor npm install simpl-schema
Usage
Methods
Example applying collection
property:
1import { createCollection } from 'meteor/quave:collections'; 2 3export const AddressesCollection = createCollection({ 4 name: 'addresses', 5 collection: { 6 save(addressParam) { 7 const address = { ...addressParam }; 8 9 if (address._id) { 10 this.update(address._id, { $set: { ...address } }); 11 return address._id; 12 } 13 delete address._id; 14 return this.insert({ ...address }); 15 }, 16 }, 17});
Schema
Example applying SimpleSchema
:
1import { createCollection } from 'meteor/quave:collections'; 2 3import SimpleSchema from 'simpl-schema'; 4 5const PlayerSchema = new SimpleSchema({ 6 name: { 7 type: String, 8 }, 9 age: { 10 type: SimpleSchema.Integer, 11 }, 12}); 13 14export const PlayersCollection = createCollection({ 15 name: 'players', 16 schema: PlayerSchema, 17});
Composers
Example creating a way to paginate the fetch of data using composers
1import { createCollection } from 'meteor/quave:collections'; 2 3const LIMIT = 7; 4export const paginable = collection => 5 Object.assign({}, collection, { 6 getPaginated({ selector, options = {}, paginationAction }) { 7 const { skip, limit } = paginationAction || { skip: 0, limit: LIMIT }; 8 const items = this.find(selector, { 9 ...options, 10 skip, 11 limit, 12 }).fetch(); 13 const total = this.find(selector).count(); 14 const nextSkip = skip + limit; 15 const previousSkip = skip - limit; 16 17 return { 18 items, 19 pagination: { 20 total, 21 totalPages: parseInt(total / limit, 10) + (total % limit > 0 ? 1 : 0), 22 currentPage: 23 parseInt(skip / limit, 10) + (skip % limit > 0 ? 1 : 0) + 1, 24 ...(nextSkip < total ? { next: { skip: nextSkip, limit } } : {}), 25 ...(previousSkip >= 0 26 ? { previous: { skip: previousSkip, limit } } 27 : {}), 28 }, 29 }; 30 }, 31 }); 32 33export const StoresCollection = createCollection({ 34 name: 'stores', 35 composers: [paginable], 36}); 37 38// This probably will come from the client, using Methods, REST, or GraphQL 39// const paginationAction = {skip: XXX, limit: YYY}; 40 41const { items, pagination } = StoresCollection.getPaginated({ 42 selector: { 43 ...(search ? { name: { $regex: search, $options: 'i' } } : {}), 44 }, 45 options: { sort: { updatedAt: -1 } }, 46 paginationAction, 47});
A different example, a little bit more complex, overriding a few methods of the original collection in order to implement a soft removal (this example only works in quave:collections@1.1.0
or later).
1import { createCollection } from 'meteor/quave:collections'; 2 3const toSelector = (filter) => { 4 if (typeof filter === 'string') { 5 return { _id: filter }; 6 } 7 return filter; 8}; 9 10const filterOptions = (options = {}) => { 11 if (options.ignoreSoftRemoved) { 12 return {}; 13 } 14 return { isRemoved: { $ne: true } }; 15}; 16 17export const softRemoval = (collection) => { 18 const originalFind = collection.find.bind(collection); 19 const originalFindOne = collection.findOne.bind(collection); 20 const originalUpdate = collection.update.bind(collection); 21 const originalRemove = collection.remove.bind(collection); 22 return Object.assign({}, collection, { 23 find(selector, options) { 24 return originalFind( 25 { 26 ...toSelector(selector), 27 ...filterOptions(options), 28 }, 29 options 30 ); 31 }, 32 findOne(selector, options) { 33 return originalFindOne( 34 { 35 ...toSelector(selector), 36 ...filterOptions(options), 37 }, 38 options 39 ); 40 }, 41 remove(selector, options = {}) { 42 if (options.hardRemove) { 43 return originalRemove(selector); 44 } 45 return originalUpdate( 46 { 47 ...toSelector(selector), 48 }, 49 { 50 $set: { 51 ...(options.$set || {}), 52 isRemoved: true, 53 removedAt: new Date(), 54 }, 55 }, 56 { multi: true } 57 ); 58 }, 59 }); 60}; 61export const SourcesCollection = createCollection({ 62 name: 'sources', 63 composers: [softRemoval], 64}); 65 66// usage example 67SourcesCollection.remove({ _id: 'KEFemSmeZ9EpNfkcH' }); // this will use the soft removal, it means, this setting `isRemoved` to true 68SourcesCollection.remove({ _id: 'KEFemSmeZ9EpNfkcH' }, { hardRemove: true }); // this will remove in the database 69
Options
Second argument for the default collections constructor. Example defining a transform function.
1const transform = doc => ({ 2 ...doc, 3 get user() { 4 return Meteor.users.findOne(this.userId); 5 }, 6}); 7 8export const PlayersCollection = createCollection({ 9 name: 'players', 10 schema, 11 options: { 12 transform, 13 }, 14});
Meteor.users
Extending Meteor.users, also using collection
, helpers
, composers
, apply
.
You can use all these options also with name
instead of instance
.
1import {createCollection} from 'meteor/quave:collections'; 2 3export const UsersCollection = createCollection({ 4 instance: Meteor.users, 5 schema: UserTypeDef, 6 collection: { 7 isAdmin(userId) { 8 const user = userId && this.findOne(userId, {fields: {profiles: 1}}); 9 return ( 10 user && user.profiles && user.profiles.includes(UserProfile.ADMIN.name) 11 ); 12 }, 13 }, 14 helpers: { 15 toPaymentGatewayJson() { 16 return { 17 country: 'us', 18 external_id: this._id, 19 name: this.name, 20 type: 'individual', 21 email: this.email, 22 }; 23 }, 24 }, 25 composers: [paginable], 26 apply(coll) { 27 coll.after.insert(userAfterInsert(coll), {fetchPrevious: false}); 28 coll.after.update(userAfterUpdate); 29 }, 30});
License
MIT