mirror of
https://github.com/vale981/grapher
synced 2025-03-04 17:11:38 -05:00
Merge branch 'master' of github.com:cult-of-coders/grapher into scoped-publish
This commit is contained in:
commit
ad017073c4
11 changed files with 227 additions and 52 deletions
27
MIGRATION.md
27
MIGRATION.md
|
@ -6,8 +6,31 @@ When you use reducers with a body that uses a link that should return a single r
|
|||
|
||||
### From 1.2 to 1.3
|
||||
|
||||
SimpleSchema has been completely removed and it will no longer extend your Collection's schema automatically, therefore,
|
||||
if you have configured links you have to manually add them.
|
||||
SimpleSchema has been completely removed and it will no longer extend your Collection's schema automatically, therefore, if you have configured links you have to manually add them.
|
||||
|
||||
For example the following link:
|
||||
|
||||
```js
|
||||
Users.addLinks({
|
||||
post: {
|
||||
type: 'one',
|
||||
collection: Posts,
|
||||
field: 'postId'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Requires the respective field in your Collection's schema:
|
||||
|
||||
```js
|
||||
// schema for Users
|
||||
SimpleSchema({
|
||||
postId: {
|
||||
type: String,
|
||||
optional: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The `metadata` link configuration is no longer an object, but a `Boolean`
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ Posts.createQuery({
|
|||
})
|
||||
```
|
||||
|
||||
The query above will fetch as `comments` only the ones that have been approved and that are linekd with the `post`.
|
||||
The query above will fetch as `comments` only the ones that have been approved and that are linked with the `post`.
|
||||
|
||||
The `$filter` function shares the same `params` across all collection nodes:
|
||||
|
||||
|
|
|
@ -5,11 +5,14 @@ const db = new Proxy(
|
|||
{},
|
||||
{
|
||||
get: function(obj, prop) {
|
||||
if (typeof prop === 'symbol') {
|
||||
return obj[prop];
|
||||
}
|
||||
|
||||
const collection = Mongo.Collection.get(prop);
|
||||
|
||||
if (!collection) {
|
||||
Meteor.isDevelopment &&
|
||||
console.warn(`There is no collection with the name: "${prop}"`);
|
||||
return obj[prop];
|
||||
}
|
||||
|
||||
return collection;
|
||||
|
|
|
@ -77,7 +77,7 @@ export default class extends Base {
|
|||
throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.');
|
||||
}
|
||||
|
||||
return await callWithPromise(this.name, prepareForProcess(this.body, this.params));
|
||||
return await callWithPromise(this.name, this.params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,7 +129,7 @@ export default class extends Base {
|
|||
throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.');
|
||||
}
|
||||
|
||||
return await callWithPromise(this.name + '.count', prepareForProcess(this.body, this.params));
|
||||
return await callWithPromise(this.name + '.count', this.params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,20 @@ describe('Named Query', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('Should return proper values using query directly via import - sync', async function() {
|
||||
const query = postListExposure.clone({ title: 'User Post - 3' });
|
||||
|
||||
const res = await query.fetchSync();
|
||||
|
||||
assert.isTrue(res.length > 0);
|
||||
|
||||
_.each(res, post => {
|
||||
assert.equal(post.title, 'User Post - 3');
|
||||
assert.isObject(post.author);
|
||||
assert.isObject(post.group);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should work with count', function(done) {
|
||||
const query = postListExposure.clone({ title: 'User Post - 3' });
|
||||
|
||||
|
@ -51,6 +65,13 @@ describe('Named Query', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('Should work with count - sync', async function() {
|
||||
const query = postListExposure.clone({ title: 'User Post - 3' });
|
||||
|
||||
const count = await query.getCountSync();
|
||||
assert.equal(6, count);
|
||||
});
|
||||
|
||||
it('Should work with reactive counts', function(done) {
|
||||
const query = postListExposure.clone({ title: 'User Post - 3' });
|
||||
|
||||
|
|
|
@ -61,23 +61,27 @@ export default function(childCollectionNode, aggregateResults, metaFilters) {
|
|||
aggregateResult._id == result._id;
|
||||
}
|
||||
|
||||
_.each(aggregateResults, aggregateResult => {
|
||||
let parentResult = _.find(
|
||||
childCollectionNode.parent.results,
|
||||
result => {
|
||||
return comparator(aggregateResult, result);
|
||||
}
|
||||
const childLinkName = childCollectionNode.linkName;
|
||||
const parentResults = childCollectionNode.parent.results;
|
||||
|
||||
parentResults.forEach(parentResult => {
|
||||
// We are now finding the data from the pipeline that is related to the _id of the parent
|
||||
const eligibleAggregateResults = aggregateResults.filter(
|
||||
aggregateResult => comparator(aggregateResult, parentResult)
|
||||
);
|
||||
|
||||
if (parentResult) {
|
||||
parentResult[childCollectionNode.linkName] =
|
||||
aggregateResult.data;
|
||||
}
|
||||
|
||||
_.each(aggregateResult.data, item => {
|
||||
allResults.push(item);
|
||||
eligibleAggregateResults.forEach(aggregateResult => {
|
||||
if (Array.isArray(parentResult[childLinkName])) {
|
||||
parentResult[childLinkName].push(...aggregateResult.data);
|
||||
} else {
|
||||
parentResult[childLinkName] = [...aggregateResult.data];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
aggregateResults.forEach(aggregateResult => {
|
||||
allResults.push(...aggregateResult.data);
|
||||
});
|
||||
}
|
||||
|
||||
childCollectionNode.results = allResults;
|
||||
|
|
|
@ -2,7 +2,11 @@ import createSearchFilters from '../../links/lib/createSearchFilters';
|
|||
import cleanObjectForMetaFilters from './lib/cleanObjectForMetaFilters';
|
||||
import sift from 'sift';
|
||||
|
||||
export default (childCollectionNode, {limit, skip, metaFilters}) => {
|
||||
export default (childCollectionNode, { limit, skip, metaFilters }) => {
|
||||
if (childCollectionNode.results.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = childCollectionNode.parent;
|
||||
const linker = childCollectionNode.linker;
|
||||
|
||||
|
@ -16,29 +20,81 @@ export default (childCollectionNode, {limit, skip, metaFilters}) => {
|
|||
if (isMeta && metaFilters) {
|
||||
const metaFiltersTest = sift(metaFilters);
|
||||
_.each(parent.results, parentResult => {
|
||||
cleanObjectForMetaFilters(parentResult, fieldStorage, metaFiltersTest);
|
||||
})
|
||||
cleanObjectForMetaFilters(
|
||||
parentResult,
|
||||
fieldStorage,
|
||||
metaFiltersTest
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_.each(parent.results, result => {
|
||||
let data = assembleData(childCollectionNode, result, {
|
||||
fieldStorage, strategy, isSingle
|
||||
const resultsByKeyId = _.groupBy(childCollectionNode.results, '_id');
|
||||
|
||||
if (strategy === 'one') {
|
||||
parent.results.forEach(parentResult => {
|
||||
if (!parentResult[fieldStorage]) {
|
||||
return;
|
||||
}
|
||||
|
||||
parentResult[childCollectionNode.linkName] = filterAssembledData(
|
||||
resultsByKeyId[parentResult[fieldStorage]],
|
||||
{ limit, skip }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
result[childCollectionNode.linkName] = filterAssembledData(data, {limit, skip})
|
||||
});
|
||||
}
|
||||
if (strategy === 'many') {
|
||||
parent.results.forEach(parentResult => {
|
||||
if (!parentResult[fieldStorage]) {
|
||||
return;
|
||||
}
|
||||
|
||||
function filterAssembledData(data, {limit, skip}) {
|
||||
if (limit) {
|
||||
let data = [];
|
||||
parentResult[fieldStorage].forEach(_id => {
|
||||
data.push(_.first(resultsByKeyId[_id]));
|
||||
});
|
||||
|
||||
parentResult[childCollectionNode.linkName] = filterAssembledData(
|
||||
data,
|
||||
{ limit, skip }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (strategy === 'one-meta') {
|
||||
parent.results.forEach(parentResult => {
|
||||
if (!parentResult[fieldStorage]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _id = parentResult[fieldStorage]._id;
|
||||
parentResult[childCollectionNode.linkName] = filterAssembledData(
|
||||
resultsByKeyId[_id],
|
||||
{ limit, skip }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (strategy === 'many-meta') {
|
||||
parent.results.forEach(parentResult => {
|
||||
const _ids = _.pluck(parentResult[fieldStorage], '_id');
|
||||
let data = [];
|
||||
_ids.forEach(_id => {
|
||||
data.push(_.first(resultsByKeyId[_id]));
|
||||
});
|
||||
|
||||
parentResult[childCollectionNode.linkName] = filterAssembledData(
|
||||
data,
|
||||
{ limit, skip }
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function filterAssembledData(data, { limit, skip }) {
|
||||
if (limit && Array.isArray(data)) {
|
||||
return data.slice(skip, limit);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function assembleData(childCollectionNode, result, {fieldStorage, strategy}) {
|
||||
const filters = createSearchFilters(result, fieldStorage, strategy, false);
|
||||
|
||||
return sift(filters, childCollectionNode.results);
|
||||
}
|
||||
|
|
0
lib/query/testing/link-cache/fixtures.js
Normal file → Executable file
0
lib/query/testing/link-cache/fixtures.js
Normal file → Executable file
3
lib/query/testing/link-cache/server.test.js
Normal file → Executable file
3
lib/query/testing/link-cache/server.test.js
Normal file → Executable file
|
@ -9,8 +9,9 @@ import {
|
|||
} from './collections';
|
||||
|
||||
describe('Query Link Denormalization', function() {
|
||||
before(() => {
|
||||
before((done) => {
|
||||
createFixtures();
|
||||
done();
|
||||
});
|
||||
|
||||
it('Should not cache work with nested options', function() {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { createQuery } from 'meteor/cultofcoders:grapher';
|
||||
import dot from 'dot-object';
|
||||
import Comments from './bootstrap/comments/collection.js';
|
||||
import Posts from './bootstrap/posts/collection.js';
|
||||
import Tags from './bootstrap/tags/collection.js';
|
||||
import './metaFilters.server.test';
|
||||
import './reducers.server.test';
|
||||
import './link-cache/server.test';
|
||||
|
||||
// Used in some tests below
|
||||
const Users = new Mongo.Collection('__many_inversed_users');
|
||||
const Restaurants = new Mongo.Collection('__many_inversed_restaurants');
|
||||
|
||||
describe('Hypernova', function() {
|
||||
it('Should fetch One links correctly', function() {
|
||||
const data = createQuery({
|
||||
|
@ -105,6 +111,71 @@ describe('Hypernova', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('Should fetch Many - inversed links correctly #2', function() {
|
||||
const post1Id = Posts.insert({ name: 'Post1' });
|
||||
const post2Id = Posts.insert({ name: 'Post2' });
|
||||
const post3Id = Posts.insert({ name: 'Post3' });
|
||||
const post4Id = Posts.insert({ name: 'Post4' });
|
||||
|
||||
const tag1Id = Tags.insert({ name: 'Tag1' });
|
||||
const tag2Id = Tags.insert({ name: 'Tag2' });
|
||||
const tag3Id = Tags.insert({ name: 'Tag3' });
|
||||
|
||||
function addTags(postId, tagIds) {
|
||||
Posts.update(postId, {
|
||||
$set: {
|
||||
tagIds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
addTags(post1Id, [tag1Id, tag2Id]);
|
||||
addTags(post2Id, [tag1Id]);
|
||||
addTags(post3Id, [tag2Id, tag3Id]);
|
||||
addTags(post4Id, [tag3Id, tag1Id]);
|
||||
|
||||
const data = createQuery({
|
||||
tags: {
|
||||
$filters: {
|
||||
_id: { $in: [tag1Id, tag2Id, tag3Id] },
|
||||
},
|
||||
name: 1,
|
||||
posts: {
|
||||
name: 1,
|
||||
},
|
||||
},
|
||||
}).fetch();
|
||||
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
|
||||
const tag1Data = _.find(data, doc => doc.name === 'Tag1');
|
||||
const tag2Data = _.find(data, doc => doc.name === 'Tag2');
|
||||
const tag3Data = _.find(data, doc => doc.name === 'Tag3');
|
||||
|
||||
function hasPost(tag, postName) {
|
||||
return !!_.find(tag.posts, post => post.name === postName);
|
||||
}
|
||||
assert.lengthOf(tag1Data.posts, 3);
|
||||
assert.isTrue(hasPost(tag1Data, 'Post1'));
|
||||
assert.isTrue(hasPost(tag1Data, 'Post2'));
|
||||
assert.isTrue(hasPost(tag1Data, 'Post4'));
|
||||
|
||||
assert.lengthOf(tag2Data.posts, 2);
|
||||
assert.isTrue(hasPost(tag2Data, 'Post1'));
|
||||
assert.isTrue(hasPost(tag2Data, 'Post3'));
|
||||
|
||||
assert.lengthOf(tag3Data.posts, 2);
|
||||
assert.isTrue(hasPost(tag3Data, 'Post3'));
|
||||
assert.isTrue(hasPost(tag3Data, 'Post4'));
|
||||
|
||||
Posts.remove({
|
||||
_id: { $in: [post1Id, post2Id, post3Id, post4Id] },
|
||||
});
|
||||
Tags.remove({
|
||||
_id: { $in: [tag1Id, tag2Id, tag3Id] },
|
||||
});
|
||||
});
|
||||
|
||||
it('Should fetch One-Meta links correctly', function() {
|
||||
const data = createQuery({
|
||||
posts: {
|
||||
|
@ -171,16 +242,16 @@ describe('Hypernova', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('Should fetch Many-Meta links correctly where parent is One link', function () {
|
||||
it('Should fetch Many-Meta links correctly where parent is One link', function() {
|
||||
const data = createQuery({
|
||||
posts: {
|
||||
$options: { limit: 5 },
|
||||
author: {
|
||||
groups: {
|
||||
isAdmin: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
isAdmin: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).fetch();
|
||||
|
||||
_.each(data, post => {
|
||||
|
@ -525,9 +596,6 @@ describe('Hypernova', function() {
|
|||
assert.isTrue(group.authors.length > 0);
|
||||
});
|
||||
|
||||
const Users = new Mongo.Collection('__many_inversed_users');
|
||||
const Restaurants = new Mongo.Collection('__many_inversed_restaurants');
|
||||
|
||||
it('Should fetch Many - inversed links correctly when the field is not the first', function() {
|
||||
Restaurants.addLinks({
|
||||
users: {
|
||||
|
@ -577,11 +645,10 @@ describe('Hypernova', function() {
|
|||
metadata: {
|
||||
language: {
|
||||
abbr: 1,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const data = query.fetch();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Package.describe({
|
||||
name: 'cultofcoders:grapher',
|
||||
version: '1.3.6',
|
||||
version: '1.3.7_4',
|
||||
// Brief, one-line summary of the package.
|
||||
summary: 'Grapher is a data fetching layer on top of Meteor',
|
||||
// URL to the Git repository containing the source code for this package.
|
||||
|
|
Loading…
Add table
Reference in a new issue