diff --git a/lib/exposure/exposure.js b/lib/exposure/exposure.js index 9cda819..2aa253d 100644 --- a/lib/exposure/exposure.js +++ b/lib/exposure/exposure.js @@ -43,10 +43,14 @@ export default class Exposure { const firewall = this.firewall; if (firewall) { - collection.findSecure = (filters, options, userId) => { + collection.firewall = (filters, options, userId) => { if (userId !== undefined) { firewall(filters, options, userId); } + }; + + collection.findSecure = (filters, options, userId) => { + collection.firewall(filters, options, userId); return collection.find(filters, options); } diff --git a/lib/links/config.schema.js b/lib/links/config.schema.js index 163b57e..20fb0a3 100644 --- a/lib/links/config.schema.js +++ b/lib/links/config.schema.js @@ -35,6 +35,11 @@ export default new SimpleSchema({ type: Boolean, defaultValue: false, optional: true + }, + unique: { + type: Boolean, + defaultValue: false, + optional: true } }); diff --git a/lib/query/hypernova/createSearchFilters.js b/lib/links/lib/createSearchFilters.js similarity index 78% rename from lib/query/hypernova/createSearchFilters.js rename to lib/links/lib/createSearchFilters.js index a9e345b..cf257c3 100644 --- a/lib/query/hypernova/createSearchFilters.js +++ b/lib/links/lib/createSearchFilters.js @@ -20,19 +20,19 @@ export default function createSearchFilters(object, fieldStorage, strategy, isVi } } -function createOne(object, fieldStorage) { +export function createOne(object, fieldStorage) { return { _id: object[fieldStorage] }; } -function createOneVirtual(object, fieldStorage) { +export function createOneVirtual(object, fieldStorage) { return { [fieldStorage]: object._id }; } -function createOneMeta(object, fieldStorage) { +export function createOneMeta(object, fieldStorage) { const value = object[fieldStorage]; return { @@ -40,13 +40,13 @@ function createOneMeta(object, fieldStorage) { }; } -function createOneMetaVirtual(object, fieldStorage) { +export function createOneMetaVirtual(object, fieldStorage) { return { [fieldStorage + '._id']: object._id }; } -function createMany(object, fieldStorage) { +export function createMany(object, fieldStorage) { return { _id: { $in: object[fieldStorage] || [] @@ -54,13 +54,13 @@ function createMany(object, fieldStorage) { }; } -function createManyVirtual(object, fieldStorage) { +export function createManyVirtual(object, fieldStorage) { return { [fieldStorage]: object._id }; } -function createManyMeta(object, fieldStorage) { +export function createManyMeta(object, fieldStorage) { const value = object[fieldStorage]; return { @@ -68,7 +68,7 @@ function createManyMeta(object, fieldStorage) { }; } -function createManyMetaVirtual(object, fieldStorage) { +export function createManyMetaVirtual(object, fieldStorage) { return { [fieldStorage + '._id']: object._id }; diff --git a/lib/links/linker.js b/lib/links/linker.js index 4cb638d..a32e48c 100644 --- a/lib/links/linker.js +++ b/lib/links/linker.js @@ -66,6 +66,10 @@ export default class Linker { */ get linkStorageField() { + if (this.isVirtual()) { + return this.linkConfig.relatedLinker.linkConfig.linkStorageField; + } + return this.linkConfig.field; } @@ -264,15 +268,42 @@ export default class Linker { } _initIndex() { - if (Meteor.isServer && this.linkConfig.index) { + if (Meteor.isServer) { let field = this.linkConfig.field; if (this.linkConfig.metadata) { - field = field + '._id'; + field = field + '._id'; } - this.mainCollection._ensureIndex({ - [field]: 1 - }) + if (this.linkConfig.index) { + if (this.isVirtual()) { + throw new Meteor.Error('You cannot set index on an inversed link.'); + } + + let options; + if (this.linkConfig.unique) { + if (this.isMany()) { + throw new Meteor.Error('You cannot set unique property on a multi field.'); + } + + options = {unique: true} + } + + this.mainCollection._ensureIndex({[field]: 1}, options); + } else { + if (this.linkConfig.unique) { + if (this.isVirtual()) { + throw new Meteor.Error('You cannot set unique property on an inversed link.'); + } + + if (this.isMany()) { + throw new Meteor.Error('You cannot set unique property on a multi field.'); + } + + this.mainCollection._ensureIndex({ + [field]: 1 + }, {unique: true}) + } + } } } diff --git a/lib/query/hypernova/aggregateSearchFilters.js b/lib/query/hypernova/aggregateSearchFilters.js index a69d76d..9d5f34c 100644 --- a/lib/query/hypernova/aggregateSearchFilters.js +++ b/lib/query/hypernova/aggregateSearchFilters.js @@ -5,11 +5,7 @@ export default class AggregateFilters { this.isVirtual = this.linker.isVirtual(); - if (this.isVirtual) { - this.linkStorageField = this.linker.linkConfig.relatedLinker.linkStorageField; - } else { - this.linkStorageField = this.linker.linkStorageField; - } + this.linkStorageField = this.linker.linkStorageField; } get parentObjects() { diff --git a/lib/query/hypernova/assembler.js b/lib/query/hypernova/assembler.js index b491220..ef2c96c 100644 --- a/lib/query/hypernova/assembler.js +++ b/lib/query/hypernova/assembler.js @@ -1,25 +1,36 @@ -import createSearchFilters from './createSearchFilters'; +import createSearchFilters from '../../links/lib/createSearchFilters'; import sift from 'sift'; -export default (childCollectionNode) => { +export default (childCollectionNode, limit) => { const parent = childCollectionNode.parent; + const linker = childCollectionNode.linker; - const strategy = childCollectionNode.linker.strategy; - const isVirtual = childCollectionNode.linker.isVirtual(); - const fieldStorage = (isVirtual) - ? childCollectionNode.linker.linkConfig.relatedLinker.linkStorageField - : childCollectionNode.linker.linkStorageField; + const strategy = linker.strategy; + const isVirtual = linker.isVirtual(); + const isSingle = linker.isSingle(); + const oneResult = (isVirtual && linker.linkConfig.relatedLinker.linkConfig.unique) + || (!isVirtual) && isSingle; + + const fieldStorage = linker.linkStorageField; _.each(parent.results, result => { result[childCollectionNode.linkName] = assembleData(childCollectionNode, result, { - fieldStorage, strategy, isVirtual - }) - }) + fieldStorage, strategy, isVirtual, isSingle, oneResult, limit + }); + }); } -function assembleData(childCollectionNode, result, {fieldStorage, strategy, isVirtual}) { - +function assembleData(childCollectionNode, result, {fieldStorage, strategy, isVirtual, oneResult, limit}) { const filters = createSearchFilters(result, fieldStorage, strategy, isVirtual); + const data = sift(filters, childCollectionNode.results); - return sift(filters, childCollectionNode.results); + if (limit) { + return data.slice(limit); + } + + if (oneResult) { + return _.first(data); + } + + return data; } \ No newline at end of file diff --git a/lib/query/hypernova/hypernova.js b/lib/query/hypernova/hypernova.js index fb8a20e..53fb468 100644 --- a/lib/query/hypernova/hypernova.js +++ b/lib/query/hypernova/hypernova.js @@ -11,15 +11,12 @@ function hypernova(collectionNode, userId) { _.each(collectionNode.results, result => { const accessor = childCollectionNode.linker.createLink(result); - childCollectionNode.results = accessor.find(filters, options); + result[childCollectionNode.linkName] = accessor.find(filters, options); }); } else { storeHypernovaResults(childCollectionNode, userId); - let start = new Date(); hypernova(childCollectionNode, userId); - let end = new Date(); - console.log(`hypernova: ${end.getTime() - start.getTime()}`); } }); } diff --git a/lib/query/hypernova/storeHypernovaResults.js b/lib/query/hypernova/storeHypernovaResults.js index 8e9c82b..13e0d57 100644 --- a/lib/query/hypernova/storeHypernovaResults.js +++ b/lib/query/hypernova/storeHypernovaResults.js @@ -16,8 +16,7 @@ export default function storeHypernovaResults(childCollectionNode, userId) { _.extend(filters, aggregateFilters.create()); - // for one, one-meta, virtual or not, there is no need for aggregate query. - // same rule applies for many, many-meta but no virtual + // if it's not virtual then we retrieve them and assemble them here. if (!isVirtual) { const filteredOptions = _.omit(options, 'limit'); @@ -28,11 +27,17 @@ export default function storeHypernovaResults(childCollectionNode, userId) { } assemble(childCollectionNode); + + return; } - // many, many-meta and virtual arrive here + // virtuals arrive here let pipeline = []; + if (collection.firewall) { + collection.firewall(filters, options, userId); + } + pipeline.push({$match: filters}); if (options.sort) { diff --git a/lib/query/lib/recursiveCompose.js b/lib/query/lib/recursiveCompose.js index d7ef811..09a154c 100644 --- a/lib/query/lib/recursiveCompose.js +++ b/lib/query/lib/recursiveCompose.js @@ -14,7 +14,7 @@ export default function compose(node, userId) { if (linker.isVirtual()) { options.fields = options.fields || {}; _.extend(options.fields, { - [linker.linkConfig.relatedLinker.linkStorageField]: 1 + [linker.linkStorageField]: 1 }); } diff --git a/lib/query/lib/recursiveFetch.js b/lib/query/lib/recursiveFetch.js index 9d9c957..5292cfd 100644 --- a/lib/query/lib/recursiveFetch.js +++ b/lib/query/lib/recursiveFetch.js @@ -1,5 +1,5 @@ import applyProps from './applyProps.js'; -import createSearchFilters from '../hypernova/createSearchFilters.js'; +import createSearchFilters from '../../links/lib/createSearchFilters.js'; import LinkResolve from '../../links/linkTypes/linkResolve.js'; import sift from 'sift'; @@ -11,24 +11,21 @@ export default function fetch(node, parentObject, userId) { if (parentObject) { if (node.linker.isResolver()) { let accessor = node.linker.createLink(parentObject, node.collection); - - if (accessor instanceof LinkResolve) { - accessor.object = node.parent.collection.findOne(parentObject._id); - } + accessor.object = node.parent.collection.findOne(parentObject._id); results = accessor.fetch(filters, options, userId); } else { const strategy = node.linker.strategy; const isVirtual = node.linker.isVirtual(); - const fieldStorage = (isVirtual) ? node.linker.linkConfig.relatedLinker.linkStorageField : node.linker.linkStorageField; + const fieldStorage = node.linker.linkStorageField; _.extend(filters, createSearchFilters(parentObject, fieldStorage, strategy, isVirtual)); - if (node.collection.findSecure) { - results = node.collection.findSecure(filters, options, userId).fetch(); - } else { - results = node.collection.find(filters, options).fetch(); - } + if (node.collection.findSecure) { + results = node.collection.findSecure(filters, options, userId).fetch(); + } else { + results = node.collection.find(filters, options).fetch(); + } } } else { if (node.collection.findSecure) { @@ -38,7 +35,6 @@ export default function fetch(node, parentObject, userId) { } } - // merge filters _.each(results, result => { _.each(node.collectionNodes, collectionNode => { result[collectionNode.linkName] = fetch(collectionNode, result, userId); @@ -46,7 +42,5 @@ export default function fetch(node, parentObject, userId) { }) }); - // assemble filters - return results; } diff --git a/lib/query/query.js b/lib/query/query.js index 414f776..04bb37c 100644 --- a/lib/query/query.js +++ b/lib/query/query.js @@ -81,9 +81,7 @@ export default class Query { applyFilterFunction(this.body, this.params) ); - //return hypernova(node, options.userId); - - return recursiveFetch(node, null, options.userId); + return hypernova(node, options.userId); } } diff --git a/lib/query/tests/bootstrap.js b/lib/query/tests/bootstrap.js index 2f6cea8..0fa1ff1 100644 --- a/lib/query/tests/bootstrap.js +++ b/lib/query/tests/bootstrap.js @@ -17,7 +17,8 @@ PostCollection.addLinks({ }, author: { collection: AuthorCollection, - type: 'one' + type: 'one', + unique: true }, comment_resolve: { resolve(object) { diff --git a/lib/query/tests/server.test.js b/lib/query/tests/server.test.js index 1d4dd60..d65e204 100644 --- a/lib/query/tests/server.test.js +++ b/lib/query/tests/server.test.js @@ -37,8 +37,6 @@ describe('Query Server Tests', function () { assert.isObject(post.author); assert.equal('John McSmithie', post.author.name); - assert.lengthOf(post.comment_resolve, 1); - _.each(post.comments, comment => { assert.isString(comment.author.name); })