mirror of
https://github.com/vale981/grapher
synced 2025-03-04 17:11:38 -05:00
Fixed #182 added named query cacher
This commit is contained in:
parent
200d2fb2cc
commit
e9167757b3
8 changed files with 129 additions and 63 deletions
|
@ -1,57 +0,0 @@
|
|||
A secure query is a query in which the form of it is locked on the server.
|
||||
Frozen queries are regarded as trusted code, the exposure from other collections will not affect them.
|
||||
Only the firewall.
|
||||
|
||||
The reason behind this concept:
|
||||
- You may have an Order for a Customer and to that order is an employee assigned
|
||||
- You want to expose all employees to admin via user exposure
|
||||
- Now, because exposures are linked you may need to add extra logic to user exposure, and it will eventually turn into a mess
|
||||
- It gets hard to validate/invalidate fields links.
|
||||
|
||||
This is the reason why you should construct your secure query and offer control over it via params. That can be used and manipulated in $filter function.
|
||||
|
||||
|
||||
```
|
||||
|
||||
const query = createNamedQuery('testList', {
|
||||
tests: {
|
||||
$filter({
|
||||
filters,
|
||||
options,
|
||||
params
|
||||
}) {
|
||||
|
||||
},
|
||||
title: 1,
|
||||
endcustomer: {
|
||||
profile: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
// In the same file or in a server-side file only:
|
||||
query.expose({
|
||||
firewall(userId, params) {
|
||||
// throw exception if not allowed
|
||||
},
|
||||
body: { // merges deeply with your current body, so you can filter without showing the client how you do it to avoid exposing precious data
|
||||
tests: {
|
||||
$filter({filters, options, params})
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```
|
||||
// You must have your collections and queries imported already.
|
||||
// Client side
|
||||
createQuery({
|
||||
testListQuery: {
|
||||
endcustomer: Meteor.userId()
|
||||
}
|
||||
})
|
||||
|
||||
```
|
44
lib/namedQuery/cache/MemoryResultCacher.js
vendored
Normal file
44
lib/namedQuery/cache/MemoryResultCacher.js
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
import {Meteor} from 'meteor/meteor';
|
||||
import cloneDeep from 'lodash.cloneDeep';
|
||||
|
||||
const DEFAULT_TTL = 60000;
|
||||
|
||||
/**
|
||||
* This is a very basic in-memory result caching functionality
|
||||
*/
|
||||
export default class MemoryResultCacher {
|
||||
constructor(config = {}) {
|
||||
this.store = {};
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
get(cacheId, {
|
||||
query,
|
||||
countCursor,
|
||||
}) {
|
||||
const cacheData = this.store[cacheId];
|
||||
if (cacheData !== undefined) {
|
||||
return cloneDeep(cacheData);
|
||||
}
|
||||
|
||||
let data;
|
||||
if (query) {
|
||||
data = query.fetch();
|
||||
} else {
|
||||
data = countCursor.count();
|
||||
}
|
||||
|
||||
this.set(cacheId, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
set(cacheId, data) {
|
||||
const ttl = this.config.ttl || DEFAULT_TTL;
|
||||
this.store[cacheId] = cloneDeep(data);
|
||||
|
||||
Meteor.setTimeout(() => {
|
||||
delete this.store[cacheId];
|
||||
}, ttl)
|
||||
}
|
||||
}
|
5
lib/namedQuery/cache/generateQueryId.js
vendored
Normal file
5
lib/namedQuery/cache/generateQueryId.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import {EJSON} from 'meteor/ejson';
|
||||
|
||||
export default function(queryName, params) {
|
||||
return `${queryName}::${EJSON.stringify(params)}`;
|
||||
}
|
|
@ -24,11 +24,15 @@ export default class NamedQueryBase {
|
|||
}
|
||||
|
||||
clone(newParams) {
|
||||
return new this.constructor(
|
||||
let clone = new this.constructor(
|
||||
this.queryName,
|
||||
this.collection,
|
||||
deepClone(this.body),
|
||||
_.extend({}, deepClone(this.params), newParams)
|
||||
);
|
||||
|
||||
clone.cacher = this.cacher;
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import prepareForProcess from '../query/lib/prepareForProcess.js';
|
||||
import Base from './namedQuery.base';
|
||||
import deepClone from 'lodash.cloneDeep';
|
||||
import MemoryResultCacher from './cache/MemoryResultCacher';
|
||||
import generateQueryId from './cache/generateQueryId';
|
||||
|
||||
export default class extends Base {
|
||||
/**
|
||||
|
@ -13,6 +15,11 @@ export default class extends Base {
|
|||
deepClone(this.params)
|
||||
);
|
||||
|
||||
if (this.cacher) {
|
||||
const cacheId = generateQueryId(this.queryName, this.params);
|
||||
return this.cacher.get(cacheId, {query});
|
||||
}
|
||||
|
||||
return query.fetch();
|
||||
}
|
||||
|
||||
|
@ -30,7 +37,15 @@ export default class extends Base {
|
|||
* @returns {any}
|
||||
*/
|
||||
getCount() {
|
||||
return this.getCursorForCounting().count();
|
||||
const countCursor = this.getCursorForCounting();
|
||||
|
||||
if (this.cacher) {
|
||||
const cacheId = 'count::' + generateQueryId(this.queryName, this.params);
|
||||
|
||||
return this.cacher.get(cacheId, {countCursor});
|
||||
}
|
||||
|
||||
return countCursor.count();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,4 +57,15 @@ export default class extends Base {
|
|||
|
||||
return this.collection.find(body.$filters || {}, {fields: {_id: 1}});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cacher
|
||||
*/
|
||||
cacheResults(cacher) {
|
||||
if (!cacher) {
|
||||
cacher = new MemoryResultCacher();
|
||||
}
|
||||
|
||||
this.cacher = cacher;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { createQuery } from 'meteor/cultofcoders:grapher';
|
||||
import { createQuery, MemoryResultCacher } from 'meteor/cultofcoders:grapher';
|
||||
import postListExposure from './queries/postListExposure.js';
|
||||
|
||||
const postList = createQuery('postList', {
|
||||
|
@ -16,6 +16,7 @@ const postList = createQuery('postList', {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
export { postList };
|
||||
export { postListExposure };
|
||||
|
||||
|
@ -28,3 +29,15 @@ postListExposure.expose({
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
const postListCached = createQuery('postListCached', {
|
||||
posts: {
|
||||
title: 1,
|
||||
}
|
||||
});
|
||||
|
||||
export {postListCached};
|
||||
|
||||
postListCached.cacheResults(new MemoryResultCacher({
|
||||
ttl: 400,
|
||||
}));
|
|
@ -1,6 +1,7 @@
|
|||
import { postList } from './bootstrap/server.js';
|
||||
import { postList, postListCached } from './bootstrap/server.js';
|
||||
import { createQuery } from 'meteor/cultofcoders:grapher';
|
||||
|
||||
|
||||
describe('Named Query', function () {
|
||||
it('Should return the proper values', function () {
|
||||
const createdQuery = createQuery({
|
||||
|
@ -42,5 +43,31 @@ describe('Named Query', function () {
|
|||
assert.isObject(post.author);
|
||||
assert.isObject(post.group);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should properly cache the values', function (done) {
|
||||
const posts = postListCached.fetch();
|
||||
const postsCount = postListCached.getCount();
|
||||
|
||||
const Posts = Mongo.Collection.get('posts');
|
||||
const postId = Posts.insert({title: 'Hello Cacher!'});
|
||||
|
||||
assert.equal(posts.length, postListCached.fetch().length);
|
||||
assert.equal(postsCount, postListCached.getCount());
|
||||
|
||||
Meteor.setTimeout(function () {
|
||||
const newPosts = postListCached.fetch();
|
||||
const newCount = postListCached.getCount();
|
||||
|
||||
Posts.remove(postId);
|
||||
|
||||
assert.isArray(newPosts);
|
||||
assert.isNumber(newCount);
|
||||
|
||||
assert.equal(posts.length + 1, newPosts.length);
|
||||
assert.equal(postsCount + 1, newCount);
|
||||
|
||||
done();
|
||||
}, 500)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,4 +21,8 @@ export {
|
|||
default as getDocumentationObject
|
||||
} from './lib/documentor/index.js';
|
||||
|
||||
export {
|
||||
default as MemoryResultCacher
|
||||
} from './lib/namedQuery/cache/MemoryResultCacher';
|
||||
|
||||
export { Types } from './lib/constants';
|
||||
|
|
Loading…
Add table
Reference in a new issue