2017-02-22 15:54:03 +01:00
|
|
|
import DataLoader from 'dataloader';
|
|
|
|
import { Mongo } from 'meteor/mongo';
|
2017-01-11 18:02:12 +01:00
|
|
|
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
2016-12-12 10:41:50 +09:00
|
|
|
import { GraphQLSchema } from './graphql.js';
|
2016-12-12 11:34:28 +09:00
|
|
|
import { Utils } from './utils.js';
|
2016-12-18 19:04:11 +09:00
|
|
|
import { runCallbacks } from './callbacks.js';
|
2016-08-08 11:18:21 +09:00
|
|
|
|
2016-10-29 14:17:57 +09:00
|
|
|
SimpleSchema.extendOptions({
|
2016-12-06 10:55:47 +01:00
|
|
|
// kept for backward compatibility?
|
|
|
|
// viewableIf: Match.Optional(Match.OneOf(Function, [String])),
|
|
|
|
// insertableIf: Match.Optional(Match.OneOf(Function, [String])),
|
|
|
|
// editableIf: Match.Optional(Match.OneOf(Function, [String])),
|
|
|
|
viewableBy: Match.Optional(Match.OneOf(Function, [String])),
|
|
|
|
insertableBy: Match.Optional(Match.OneOf(Function, [String])),
|
|
|
|
editableBy: Match.Optional(Match.OneOf(Function, [String])),
|
2016-11-08 14:56:39 +09:00
|
|
|
resolveAs: Match.Optional(String),
|
2016-11-15 15:59:34 +09:00
|
|
|
publish: Match.Optional(Boolean),
|
2016-10-29 14:17:57 +09:00
|
|
|
});
|
|
|
|
|
2015-05-10 13:37:42 +09:00
|
|
|
/**
|
2016-04-09 09:41:20 +09:00
|
|
|
* @summary Meteor Collections.
|
2015-05-10 13:37:42 +09:00
|
|
|
* @class Mongo.Collection
|
|
|
|
*/
|
|
|
|
|
2015-04-24 09:28:50 +09:00
|
|
|
/**
|
2016-04-09 09:41:20 +09:00
|
|
|
* @summary @summary Add an additional field (or an array of fields) to a schema.
|
2015-05-18 18:32:54 +09:00
|
|
|
* @param {Object|Object[]} field
|
2015-04-24 09:28:50 +09:00
|
|
|
*/
|
2015-05-18 18:32:54 +09:00
|
|
|
Mongo.Collection.prototype.addField = function (fieldOrFieldArray) {
|
2015-04-24 09:28:50 +09:00
|
|
|
|
2017-01-21 16:56:54 +09:00
|
|
|
const collection = this;
|
|
|
|
const schema = collection.simpleSchema()._schema;
|
|
|
|
const fieldSchema = {};
|
2015-05-01 18:22:00 +02:00
|
|
|
|
2017-01-21 16:56:54 +09:00
|
|
|
const fieldArray = Array.isArray(fieldOrFieldArray) ? fieldOrFieldArray : [fieldOrFieldArray];
|
2015-05-18 18:32:54 +09:00
|
|
|
|
2017-01-21 16:56:54 +09:00
|
|
|
// loop over fields and add them to schema (or extend existing fields)
|
2015-05-18 18:32:54 +09:00
|
|
|
fieldArray.forEach(function (field) {
|
2017-01-21 16:56:54 +09:00
|
|
|
const newField = {...schema[field.fieldName], ...field.fieldSchema};
|
|
|
|
fieldSchema[field.fieldName] = newField;
|
2015-05-18 18:32:54 +09:00
|
|
|
});
|
2015-04-24 09:28:50 +09:00
|
|
|
|
|
|
|
// add field schema to collection schema
|
|
|
|
collection.attachSchema(fieldSchema);
|
2015-05-01 18:22:00 +02:00
|
|
|
};
|
2015-04-27 09:55:29 +09:00
|
|
|
|
2015-04-27 10:30:47 +09:00
|
|
|
/**
|
2016-04-09 09:41:20 +09:00
|
|
|
* @summary Remove a field from a schema.
|
2015-04-27 10:30:47 +09:00
|
|
|
* @param {String} fieldName
|
|
|
|
*/
|
2015-05-10 13:37:42 +09:00
|
|
|
Mongo.Collection.prototype.removeField = function (fieldName) {
|
2015-04-27 10:30:47 +09:00
|
|
|
|
|
|
|
var collection = this;
|
|
|
|
var schema = _.omit(collection.simpleSchema()._schema, fieldName);
|
|
|
|
|
|
|
|
// add field schema to collection schema
|
|
|
|
collection.attachSchema(schema, {replace: true});
|
2015-05-01 18:22:00 +02:00
|
|
|
};
|
2015-04-27 10:30:47 +09:00
|
|
|
|
2015-04-27 09:55:29 +09:00
|
|
|
/**
|
2016-04-09 09:41:20 +09:00
|
|
|
* @summary Global schemas object. Note: not reactive, won't be updated after initialization
|
2015-04-27 09:55:29 +09:00
|
|
|
*/
|
2015-04-28 17:15:53 +09:00
|
|
|
|
2015-06-05 10:55:52 +09:00
|
|
|
SimpleSchema.prototype.getProfileFields = function () {
|
|
|
|
var schema = this._schema;
|
|
|
|
var fields = _.filter(_.keys(schema), function (fieldName) {
|
|
|
|
var field = schema[fieldName];
|
|
|
|
return !!field.profile;
|
|
|
|
});
|
|
|
|
return fields;
|
2016-04-21 15:09:03 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @summary Get a list of a schema's private fields
|
|
|
|
*/
|
|
|
|
Mongo.Collection.prototype.getPrivateFields = function () {
|
|
|
|
var schema = this.simpleSchema()._schema;
|
|
|
|
var fields = _.filter(_.keys(schema), function (fieldName) {
|
|
|
|
var field = schema[fieldName];
|
|
|
|
return field.publish !== true;
|
|
|
|
});
|
|
|
|
return fields;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @summary Get a list of a schema's public fields
|
|
|
|
*/
|
|
|
|
Mongo.Collection.prototype.getPublicFields = function () {
|
|
|
|
var schema = this.simpleSchema()._schema;
|
|
|
|
var fields = _.filter(_.keys(schema), function (fieldName) {
|
|
|
|
var field = schema[fieldName];
|
|
|
|
return field.publish === true;
|
|
|
|
});
|
|
|
|
return fields;
|
|
|
|
};
|
|
|
|
|
2016-12-12 10:24:34 +09:00
|
|
|
export const createCollection = options => {
|
2016-11-21 16:18:08 +09:00
|
|
|
|
|
|
|
// initialize new Mongo collection
|
2016-11-22 18:14:51 -05:00
|
|
|
const collection = options.collectionName === 'users' ? Meteor.users : new Mongo.Collection(options.collectionName);
|
2016-11-21 16:18:08 +09:00
|
|
|
|
|
|
|
// decorate collection with options
|
|
|
|
collection.options = options;
|
|
|
|
|
|
|
|
// add typeName
|
|
|
|
collection.typeName = options.typeName;
|
|
|
|
|
2016-11-27 19:12:54 +09:00
|
|
|
if (options.schema) {
|
|
|
|
// attach schema to collection
|
|
|
|
collection.attachSchema(new SimpleSchema(options.schema));
|
|
|
|
}
|
2016-11-21 16:18:08 +09:00
|
|
|
|
|
|
|
// add collection to list of dynamically generated GraphQL schemas
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addCollection(collection);
|
2016-11-21 16:18:08 +09:00
|
|
|
|
|
|
|
// add collection to resolver context
|
|
|
|
const context = {};
|
2017-02-22 15:54:03 +01:00
|
|
|
|
2016-12-12 11:34:28 +09:00
|
|
|
context[Utils.capitalize(options.collectionName)] = collection;
|
2017-02-22 15:54:03 +01:00
|
|
|
|
|
|
|
context[`Batching${Utils.capitalize(options.collectionName)}`] = new BatchingCollection(collection);
|
|
|
|
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addToContext(context);
|
2016-11-21 16:18:08 +09:00
|
|
|
|
|
|
|
// ------------------------------------- Queries -------------------------------- //
|
|
|
|
|
2016-11-27 19:12:54 +09:00
|
|
|
if (options.resolvers) {
|
|
|
|
const queryResolvers = {};
|
|
|
|
// list
|
|
|
|
if (options.resolvers.list) { // e.g. ""
|
2016-12-20 15:19:13 +01:00
|
|
|
GraphQLSchema.addQuery(`${options.resolvers.list.name}(terms: JSON, offset: Int, limit: Int): [${options.typeName}]`);
|
2016-11-27 19:12:54 +09:00
|
|
|
queryResolvers[options.resolvers.list.name] = options.resolvers.list.resolver;
|
|
|
|
}
|
|
|
|
// single
|
|
|
|
if (options.resolvers.single) {
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addQuery(`${options.resolvers.single.name}(documentId: String, slug: String): ${options.typeName}`);
|
2016-11-27 19:12:54 +09:00
|
|
|
queryResolvers[options.resolvers.single.name] = options.resolvers.single.resolver;
|
|
|
|
}
|
|
|
|
// total
|
|
|
|
if (options.resolvers.total) {
|
2016-12-20 15:19:13 +01:00
|
|
|
GraphQLSchema.addQuery(`${options.resolvers.total.name}(terms: JSON): Int`);
|
2016-11-27 19:12:54 +09:00
|
|
|
queryResolvers[options.resolvers.total.name] = options.resolvers.total.resolver;
|
|
|
|
}
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addResolvers({ Query: { ...queryResolvers } });
|
2016-11-21 16:18:08 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------- Mutations -------------------------------- //
|
|
|
|
|
2016-11-27 19:12:54 +09:00
|
|
|
if (options.mutations) {
|
|
|
|
const mutations = {};
|
|
|
|
// new
|
|
|
|
if (options.mutations.new) { // e.g. "moviesNew(document: moviesInput) : Movie"
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addMutation(`${options.mutations.new.name}(document: ${options.collectionName}Input) : ${options.typeName}`);
|
2016-11-27 19:12:54 +09:00
|
|
|
mutations[options.mutations.new.name] = options.mutations.new.mutation.bind(options.mutations.new);
|
|
|
|
}
|
|
|
|
// edit
|
|
|
|
if (options.mutations.edit) { // e.g. "moviesEdit(documentId: String, set: moviesInput, unset: moviesUnset) : Movie"
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addMutation(`${options.mutations.edit.name}(documentId: String, set: ${options.collectionName}Input, unset: ${options.collectionName}Unset) : ${options.typeName}`);
|
2016-11-27 19:12:54 +09:00
|
|
|
mutations[options.mutations.edit.name] = options.mutations.edit.mutation.bind(options.mutations.edit);
|
|
|
|
}
|
|
|
|
// remove
|
|
|
|
if (options.mutations.remove) { // e.g. "moviesRemove(documentId: String) : Movie"
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addMutation(`${options.mutations.remove.name}(documentId: String) : ${options.typeName}`);
|
2016-11-27 19:12:54 +09:00
|
|
|
mutations[options.mutations.remove.name] = options.mutations.remove.mutation.bind(options.mutations.remove);
|
|
|
|
}
|
2016-12-12 10:41:50 +09:00
|
|
|
GraphQLSchema.addResolvers({ Mutation: { ...mutations } });
|
2016-11-21 16:18:08 +09:00
|
|
|
}
|
2016-11-27 19:12:54 +09:00
|
|
|
|
2016-12-18 19:04:11 +09:00
|
|
|
// ------------------------------------- Parameters -------------------------------- //
|
|
|
|
|
2017-01-29 09:51:38 +09:00
|
|
|
collection.getParameters = (terms = {}, apolloClient) => {
|
2016-12-18 19:04:11 +09:00
|
|
|
|
2016-12-20 09:27:16 +09:00
|
|
|
// console.log(terms)
|
2016-12-18 19:04:11 +09:00
|
|
|
|
|
|
|
let parameters = {
|
|
|
|
selector: {},
|
|
|
|
options: {}
|
|
|
|
};
|
|
|
|
|
|
|
|
// iterate over posts.parameters callbacks
|
2017-01-29 09:51:38 +09:00
|
|
|
parameters = runCallbacks(`${options.collectionName}.parameters`, parameters, _.clone(terms), apolloClient);
|
2016-12-18 19:04:11 +09:00
|
|
|
|
|
|
|
// extend sort to sort posts by _id to break ties
|
|
|
|
// NOTE: always do this last to avoid _id sort overriding another sort
|
|
|
|
parameters = Utils.deepExtend(true, parameters, {options: {sort: {_id: -1}}});
|
|
|
|
|
2016-12-20 09:27:16 +09:00
|
|
|
// console.log(parameters);
|
2016-12-18 19:04:11 +09:00
|
|
|
|
|
|
|
return parameters;
|
|
|
|
}
|
|
|
|
|
2016-11-21 16:18:08 +09:00
|
|
|
return collection;
|
2016-12-20 15:19:13 +01:00
|
|
|
}
|
2017-02-22 15:54:03 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @summary Find by ids, for DataLoader, inspired by https://github.com/tmeasday/mongo-find-by-ids/blob/master/index.js
|
|
|
|
*/
|
|
|
|
const findByIds = async function(collection, ids) {
|
|
|
|
try {
|
|
|
|
const docs = await collection.find({ _id: { $in: ids } }).fetch();
|
|
|
|
const idMap = {};
|
|
|
|
|
|
|
|
docs.forEach(doc => { idMap[doc._id] = doc; });
|
|
|
|
|
|
|
|
return ids.map(id => idMap[id]);
|
|
|
|
} catch(error) {
|
|
|
|
throw Error(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class BatchingCollection {
|
|
|
|
constructor(collection) {
|
|
|
|
this.collection = collection;
|
|
|
|
this.loader = new DataLoader(ids => findByIds(collection, ids));
|
|
|
|
}
|
|
|
|
|
|
|
|
findOne({ _id }) {
|
|
|
|
// note: do not handle slug atm
|
|
|
|
return this.loader.load(_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
async find(selector, options) {
|
|
|
|
const docs = await this.collection.find(selector, options).fetch();
|
|
|
|
return docs;
|
|
|
|
}
|
|
|
|
|
|
|
|
async insert(doc) {
|
|
|
|
const _id = (await this.collection.insert(doc)).insertedId;
|
|
|
|
|
|
|
|
return _id;
|
|
|
|
}
|
|
|
|
|
|
|
|
async update({ _id }, modifier) {
|
|
|
|
const ret = await this.collection.update({ _id }, modifier);
|
|
|
|
this.loader.clear(_id);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
async remove({ _id }) {
|
|
|
|
const ret = this.collection.remove({ _id });
|
|
|
|
this.loader.clear(_id);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|