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
|
### From 1.2 to 1.3
|
||||||
|
|
||||||
SimpleSchema has been completely removed and it will no longer extend your Collection's schema automatically, therefore,
|
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.
|
||||||
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`
|
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:
|
The `$filter` function shares the same `params` across all collection nodes:
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,14 @@ const db = new Proxy(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
get: function(obj, prop) {
|
get: function(obj, prop) {
|
||||||
|
if (typeof prop === 'symbol') {
|
||||||
|
return obj[prop];
|
||||||
|
}
|
||||||
|
|
||||||
const collection = Mongo.Collection.get(prop);
|
const collection = Mongo.Collection.get(prop);
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
Meteor.isDevelopment &&
|
return obj[prop];
|
||||||
console.warn(`There is no collection with the name: "${prop}"`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return collection;
|
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.');
|
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.');
|
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) {
|
it('Should work with count', function(done) {
|
||||||
const query = postListExposure.clone({ title: 'User Post - 3' });
|
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) {
|
it('Should work with reactive counts', function(done) {
|
||||||
const query = postListExposure.clone({ title: 'User Post - 3' });
|
const query = postListExposure.clone({ title: 'User Post - 3' });
|
||||||
|
|
||||||
|
|
|
@ -61,23 +61,27 @@ export default function(childCollectionNode, aggregateResults, metaFilters) {
|
||||||
aggregateResult._id == result._id;
|
aggregateResult._id == result._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
_.each(aggregateResults, aggregateResult => {
|
const childLinkName = childCollectionNode.linkName;
|
||||||
let parentResult = _.find(
|
const parentResults = childCollectionNode.parent.results;
|
||||||
childCollectionNode.parent.results,
|
|
||||||
result => {
|
parentResults.forEach(parentResult => {
|
||||||
return comparator(aggregateResult, result);
|
// 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) {
|
eligibleAggregateResults.forEach(aggregateResult => {
|
||||||
parentResult[childCollectionNode.linkName] =
|
if (Array.isArray(parentResult[childLinkName])) {
|
||||||
aggregateResult.data;
|
parentResult[childLinkName].push(...aggregateResult.data);
|
||||||
}
|
} else {
|
||||||
|
parentResult[childLinkName] = [...aggregateResult.data];
|
||||||
_.each(aggregateResult.data, item => {
|
}
|
||||||
allResults.push(item);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
aggregateResults.forEach(aggregateResult => {
|
||||||
|
allResults.push(...aggregateResult.data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
childCollectionNode.results = allResults;
|
childCollectionNode.results = allResults;
|
||||||
|
|
|
@ -2,7 +2,11 @@ import createSearchFilters from '../../links/lib/createSearchFilters';
|
||||||
import cleanObjectForMetaFilters from './lib/cleanObjectForMetaFilters';
|
import cleanObjectForMetaFilters from './lib/cleanObjectForMetaFilters';
|
||||||
import sift from 'sift';
|
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 parent = childCollectionNode.parent;
|
||||||
const linker = childCollectionNode.linker;
|
const linker = childCollectionNode.linker;
|
||||||
|
|
||||||
|
@ -16,29 +20,81 @@ export default (childCollectionNode, {limit, skip, metaFilters}) => {
|
||||||
if (isMeta && metaFilters) {
|
if (isMeta && metaFilters) {
|
||||||
const metaFiltersTest = sift(metaFilters);
|
const metaFiltersTest = sift(metaFilters);
|
||||||
_.each(parent.results, parentResult => {
|
_.each(parent.results, parentResult => {
|
||||||
cleanObjectForMetaFilters(parentResult, fieldStorage, metaFiltersTest);
|
cleanObjectForMetaFilters(
|
||||||
})
|
parentResult,
|
||||||
|
fieldStorage,
|
||||||
|
metaFiltersTest
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_.each(parent.results, result => {
|
const resultsByKeyId = _.groupBy(childCollectionNode.results, '_id');
|
||||||
let data = assembleData(childCollectionNode, result, {
|
|
||||||
fieldStorage, strategy, isSingle
|
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}) {
|
let data = [];
|
||||||
if (limit) {
|
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.slice(skip, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
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';
|
} from './collections';
|
||||||
|
|
||||||
describe('Query Link Denormalization', function() {
|
describe('Query Link Denormalization', function() {
|
||||||
before(() => {
|
before((done) => {
|
||||||
createFixtures();
|
createFixtures();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should not cache work with nested options', function() {
|
it('Should not cache work with nested options', function() {
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import { createQuery } from 'meteor/cultofcoders:grapher';
|
import { createQuery } from 'meteor/cultofcoders:grapher';
|
||||||
import dot from 'dot-object';
|
import dot from 'dot-object';
|
||||||
import Comments from './bootstrap/comments/collection.js';
|
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 './metaFilters.server.test';
|
||||||
import './reducers.server.test';
|
import './reducers.server.test';
|
||||||
import './link-cache/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() {
|
describe('Hypernova', function() {
|
||||||
it('Should fetch One links correctly', function() {
|
it('Should fetch One links correctly', function() {
|
||||||
const data = createQuery({
|
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() {
|
it('Should fetch One-Meta links correctly', function() {
|
||||||
const data = createQuery({
|
const data = createQuery({
|
||||||
posts: {
|
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({
|
const data = createQuery({
|
||||||
posts: {
|
posts: {
|
||||||
$options: { limit: 5 },
|
$options: { limit: 5 },
|
||||||
author: {
|
author: {
|
||||||
groups: {
|
groups: {
|
||||||
isAdmin: 1
|
isAdmin: 1,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}).fetch();
|
}).fetch();
|
||||||
|
|
||||||
_.each(data, post => {
|
_.each(data, post => {
|
||||||
|
@ -525,9 +596,6 @@ describe('Hypernova', function() {
|
||||||
assert.isTrue(group.authors.length > 0);
|
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() {
|
it('Should fetch Many - inversed links correctly when the field is not the first', function() {
|
||||||
Restaurants.addLinks({
|
Restaurants.addLinks({
|
||||||
users: {
|
users: {
|
||||||
|
@ -577,11 +645,10 @@ describe('Hypernova', function() {
|
||||||
metadata: {
|
metadata: {
|
||||||
language: {
|
language: {
|
||||||
abbr: 1,
|
abbr: 1,
|
||||||
}
|
},
|
||||||
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = query.fetch();
|
const data = query.fetch();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'cultofcoders:grapher',
|
name: 'cultofcoders:grapher',
|
||||||
version: '1.3.6',
|
version: '1.3.7_4',
|
||||||
// Brief, one-line summary of the package.
|
// Brief, one-line summary of the package.
|
||||||
summary: 'Grapher is a data fetching layer on top of Meteor',
|
summary: 'Grapher is a data fetching layer on top of Meteor',
|
||||||
// URL to the Git repository containing the source code for this package.
|
// URL to the Git repository containing the source code for this package.
|
||||||
|
|
Loading…
Add table
Reference in a new issue