From 7b311882b2aa2a7d039da6d3ebe1272093b0ce56 Mon Sep 17 00:00:00 2001 From: Theodor Diaconu Date: Tue, 29 Nov 2016 10:52:42 +0200 Subject: [PATCH 1/4] Fixed #63 - added support for promises and sync values --- lib/exposure/exposure.js | 36 +++++++++++++++++------------ lib/namedQuery/namedQuery.client.js | 22 ++++++++++++++++++ lib/query/lib/callWithPromise.js | 9 ++++++++ lib/query/query.client.js | 31 ++++++++++++++++++++++++- lib/query/testing/client.test.js | 25 ++++++++++++++++++++ main.server.js | 6 ----- package.js | 2 +- 7 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 lib/query/lib/callWithPromise.js diff --git a/lib/exposure/exposure.js b/lib/exposure/exposure.js index 6b526a8..b813b04 100644 --- a/lib/exposure/exposure.js +++ b/lib/exposure/exposure.js @@ -133,22 +133,28 @@ export default class Exposure { const config = this.config; const getTransformedBody = this.getTransformedBody.bind(this); + const methodBody = function(body) { + if (!config.blocking) { + this.unblock(); + } + + let transformedBody = getTransformedBody(body); + + const rootNode = createGraph(collection, transformedBody); + + enforceMaxDepth(rootNode, config.maxDepth); + restrictLinks(rootNode, this.userId); + + // if there is no exposure body defined, then we need to apply firewalls + return hypernova(rootNode, this.userId, { + bypassFirewalls: !!config.body + }); + }; + Meteor.methods({ - [this.name](body) { - if (!config.blocking) { - this.unblock(); - } - - let transformedBody = getTransformedBody(body); - - const rootNode = createGraph(collection, transformedBody); - enforceMaxDepth(rootNode, config.maxDepth); - - restrictLinks(rootNode, this.userId); - - return hypernova(rootNode, this.userId, { - bypassFirewalls: !!config.body - }); + [this.name]: methodBody, + [this.name + '_async']: async function(...args) { + return methodBody.call(this, ...args); } }); } diff --git a/lib/namedQuery/namedQuery.client.js b/lib/namedQuery/namedQuery.client.js index 30f5d94..cdbb6c0 100644 --- a/lib/namedQuery/namedQuery.client.js +++ b/lib/namedQuery/namedQuery.client.js @@ -1,6 +1,8 @@ import createGraph from '../query/lib/createGraph.js'; import recursiveFetch from '../query/lib/recursiveFetch.js'; import prepareForProcess from '../query/lib/prepareForProcess.js'; +import { _ } from 'meteor/underscore'; +import callWithPromise from '../query/lib/callWithPromise'; import Base from './namedQuery.base'; export default class extends Base { @@ -31,6 +33,26 @@ export default class extends Base { this.subscriptionHandle = null; } + /** + * Fetches elements in sync using promises + * @return {*} + */ + async fetchSync() { + if (this.subscriptionHandle) { + throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.'); + } + + return await callWithPromise(this.name + '_async', prepareForProcess(this.body, this.params)); + } + + /** + * Fetches one element in sync + * @return {*} + */ + async fetchOneSync() { + return _.first(await this.fetchSync()) + } + /** * Retrieves the data. * @param callbackOrOptions diff --git a/lib/query/lib/callWithPromise.js b/lib/query/lib/callWithPromise.js new file mode 100644 index 0000000..536e9e2 --- /dev/null +++ b/lib/query/lib/callWithPromise.js @@ -0,0 +1,9 @@ +export default (method, myParameters) => { + return new Promise((resolve, reject) => { + Meteor.call(method, myParameters, (err, res) => { + if (err) reject(err.reason || 'Something went wrong.'); + + resolve(res); + }); + }); +}; \ No newline at end of file diff --git a/lib/query/query.client.js b/lib/query/query.client.js index 76108df..48f7764 100644 --- a/lib/query/query.client.js +++ b/lib/query/query.client.js @@ -1,7 +1,8 @@ +import { _ } from 'meteor/underscore'; import createGraph from './lib/createGraph.js'; import recursiveFetch from './lib/recursiveFetch.js'; import prepareForProcess from './lib/prepareForProcess.js'; -import deepClone from './lib/deepClone.js'; +import callWithPromise from './lib/callWithPromise'; import Base from './query.base'; export default class Query extends Base { @@ -32,6 +33,26 @@ export default class Query extends Base { this.subscriptionHandle = null; } + /** + * Fetches elements in sync using promises + * @return {*} + */ + async fetchSync() { + if (this.subscriptionHandle) { + throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.'); + } + + return await callWithPromise(this.name + '_async', prepareForProcess(this.body, this.params)); + } + + /** + * Fetches one element in sync + * @return {*} + */ + async fetchOneSync() { + return _.first(await this.fetchSync()) + } + /** * Retrieves the data. * @param callbackOrOptions @@ -45,6 +66,14 @@ export default class Query extends Base { } } + /** + * @param args + * @returns {*} + */ + fetchOne(...args) { + return _.first(this.fetch(...args)); + } + /** * Gets the count of matching elements. * @param callback diff --git a/lib/query/testing/client.test.js b/lib/query/testing/client.test.js index 499930e..9069f9b 100644 --- a/lib/query/testing/client.test.js +++ b/lib/query/testing/client.test.js @@ -186,4 +186,29 @@ describe('Query Client Tests', function () { } }); }); + + it('Should work with promises', async function () { + let query = createQuery({ + groups: { + posts: { + title: 1 + } + } + }); + + let result = await query.fetchSync(); + + assert.isArray(result); + assert.isTrue(result.length > 0); + result.forEach(item => { + assert.isArray(item.posts); + assert.isTrue(item.posts.length > 0); + }); + + result = await query.fetchOneSync(); + + assert.isObject(result); + assert.isString(result._id); + assert.isArray(result.posts); + }) }); \ No newline at end of file diff --git a/main.server.js b/main.server.js index d141006..01d134c 100644 --- a/main.server.js +++ b/main.server.js @@ -5,12 +5,6 @@ import './lib/query/reducers/extension.js'; import './lib/namedQuery/expose/extension.js'; import './lib/namedQuery/extension.js'; -import { checkNpmVersions } from 'meteor/tmeasday:check-npm-versions'; - -checkNpmVersions({ - 'sift': '3.2.x' -}, 'cultofcoders:grapher'); - export { default as createQuery } from './lib/query/createQuery.js'; diff --git a/package.js b/package.js index 36f4ebe..6152c48 100644 --- a/package.js +++ b/package.js @@ -70,7 +70,7 @@ Package.onTest(function (api) { api.addFiles('lib/query/testing/bootstrap/index.js'); // When you play with tests you should comment this to make tests go faster. - api.addFiles('lib/query/testing/bootstrap/fixtures.js', 'server'); + // api.addFiles('lib/query/testing/bootstrap/fixtures.js', 'server'); api.addFiles('lib/query/testing/server.test.js', 'server'); api.addFiles('lib/query/testing/client.test.js', 'client'); From def7463821c2e29c49189f432f25975cb8285511 Mon Sep 17 00:00:00 2001 From: Theodor Diaconu Date: Tue, 29 Nov 2016 11:16:23 +0200 Subject: [PATCH 2/4] Fixed #63 - enabling auto-remove from the inverse side as well --- lib/links/extension.js | 4 ++++ lib/links/linker.js | 23 +++++++++++++++++++---- lib/links/tests/main.js | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/lib/links/extension.js b/lib/links/extension.js index 3427b06..d80e258 100644 --- a/lib/links/extension.js +++ b/lib/links/extension.js @@ -61,6 +61,10 @@ _.extend(Mongo.Collection.prototype, { } else { object = {_id: objectOrId}; } + + if (!object) { + throw new Meteor.Error(`We could not find any object with _id: "${objectOrId}" within the collection: ${this._name}`); + } } return linkData[name].createLink(object); diff --git a/lib/links/linker.js b/lib/links/linker.js index fe92fca..658bc55 100644 --- a/lib/links/linker.js +++ b/lib/links/linker.js @@ -24,12 +24,18 @@ export default class Linker { this._validateAndClean(); this._extendSchema(); - // if it's a virtual field make sure that when this is deleted, it will be removed from the references + // initialize cascade removal hooks. + if (linkConfig.autoremove) { + this._initAutoremove(); + } + if (this.isVirtual()) { - this._handleReferenceRemovalForVirtualLinks(); + // if it's a virtual field make sure that when this is deleted, it will be removed from the references + if (!linkConfig.autoremove) { + this._handleReferenceRemovalForVirtualLinks(); + } } else { this._initIndex(); - this._initAutoremove(); } } @@ -343,7 +349,7 @@ export default class Linker { } _initAutoremove() { - if (this.linkConfig.autoremove) { + if (!this.isVirtual()) { this.mainCollection.after.remove((userId, doc) => { this.getLinkedCollection().remove({ _id: { @@ -351,6 +357,15 @@ export default class Linker { } }) }) + } else { + this.mainCollection.after.remove((userId, doc) => { + const linker = this.mainCollection.getLink(doc, this.linkName); + const ids = linker.find({}, {fields: {_id: 1}}).fetch().map(item => item._id); + + this.getLinkedCollection().remove({ + _id: {$in: ids} + }) + }) } } } \ No newline at end of file diff --git a/lib/links/tests/main.js b/lib/links/tests/main.js index c06cbda..10e96f1 100644 --- a/lib/links/tests/main.js +++ b/lib/links/tests/main.js @@ -36,6 +36,11 @@ PostCollection.addLinks({ field: 'autoRemoveIds', autoremove: true }, + 'autoRemovingSelfComments': { + type: '*', + collection: CommentCollection, + field: 'autoRemovingSelfCommentsIds', + }, 'metaComments': { type: '*', collection: CommentCollection, @@ -69,6 +74,11 @@ CommentCollection.addLinks({ collection: PostCollection, inversedBy: 'comments' }, + autoRemovePosts: { + collection: PostCollection, + inversedBy: 'autoRemovingSelfComments', + autoremove: true + }, metaPost: { collection: PostCollection, inversedBy: 'metaComments' @@ -344,5 +354,34 @@ describe('Collection Links', function () { assert.equal(e.error, 'not-found'); done(); } + }); + + it('Should work with autoremoval from inversed and direct link', function () { + // autoremoval from direct side + let postId = PostCollection.insert({text: 'autoremove'}); + const postAutoRemoveCommentsLink = PostCollection.getLink(postId, 'autoRemoveComments'); + + postAutoRemoveCommentsLink.add({text: 'hello'}); + + assert.lengthOf(postAutoRemoveCommentsLink.find().fetch(), 1); + let commentId = postAutoRemoveCommentsLink.find().fetch()[0]._id; + + assert.isObject(CommentCollection.findOne(commentId)); + PostCollection.remove(postId); + assert.isUndefined(CommentCollection.findOne(commentId)); + + + // now from inversed side + commentId = CommentCollection.insert({text: 'autoremove'}); + + const commentAutoRemovePostsLink = CommentCollection.getLink(commentId, 'autoRemovePosts'); + commentAutoRemovePostsLink.add({text: 'Hello'}); + + assert.lengthOf(commentAutoRemovePostsLink.find().fetch(), 1); + postId = commentAutoRemovePostsLink.find().fetch()[0]._id; + + assert.isObject(PostCollection.findOne(postId)); + CommentCollection.remove(commentId); + assert.isUndefined(PostCollection.findOne(postId)); }) }); \ No newline at end of file From 9e363af1ffdd910f127f6a9154bdf5a4c5abe48d Mon Sep 17 00:00:00 2001 From: Theodor Diaconu Date: Tue, 29 Nov 2016 11:17:12 +0200 Subject: [PATCH 3/4] bumped to 1.2.5 --- CHANGELOG.md | 5 +++++ package.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beced89..35609fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.2.5 +- Support for promises via .fetchSync and .fetchOneSync for client-side queries +- Support for autoremove from inverse side as well +- Fixed .fetchOne from client-side Query + ## 1.2.4 - Fixed #55, #60, #61, #66 - Added Reducers Concept diff --git a/package.js b/package.js index 6152c48..3ea9148 100644 --- a/package.js +++ b/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'cultofcoders:grapher', - version: '1.2.4', + version: '1.2.5', // Brief, one-line summary of the package. summary: 'Grapher makes linking collections easily. And fetching data as a graph.', // URL to the Git repository containing the source code for this package. From a116fd9771a2f938c5862d0ebbea7a02b21ef803 Mon Sep 17 00:00:00 2001 From: Theodor Diaconu Date: Tue, 29 Nov 2016 11:21:35 +0200 Subject: [PATCH 4/4] bumped to 1.2.5 --- package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.js b/package.js index 3ea9148..35f4429 100644 --- a/package.js +++ b/package.js @@ -70,7 +70,7 @@ Package.onTest(function (api) { api.addFiles('lib/query/testing/bootstrap/index.js'); // When you play with tests you should comment this to make tests go faster. - // api.addFiles('lib/query/testing/bootstrap/fixtures.js', 'server'); + api.addFiles('lib/query/testing/bootstrap/fixtures.js', 'server'); api.addFiles('lib/query/testing/server.test.js', 'server'); api.addFiles('lib/query/testing/client.test.js', 'client');