├── LICENSE ├── README.md ├── package.js └── server.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Max Savin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # userCache for Meteor 2 | 3 | userCache builds upon three simple premises: 4 | 1. Every time you run Meteor.user() on the server, it has to call the database to retrieve the user document 5 | 2. Every time a user account connects to your server, Meteor automatically subscribes for to their user document 6 | 3. Whenever a subscription is running, the documents that are part of that subscription are cached to the server 7 | 8 | Thus, instead of querying the database every time that `Meteor.user()` is ran, we could first see if it's sufficient to retrieve it from the server-side cache (also known as MergeBox). Since MergeBox is fast and real-time (in fact, it gets the data before the client), the risk of stale data may be insignificant. 9 | 10 | The benefit of using userCache are: 11 | - fewer database queries and the associated overhead 12 | - faster performance and response time 13 | - complete backwards compatibility 14 | 15 | ## How to Use 16 | 17 | To add the package, run: 18 | 19 | ```bash 20 | meteor add msavin:usercache 21 | ``` 22 | 23 | Once you add the package, Meteor.user() will be enhanced with new properties. 24 | 25 | ```javascript 26 | Meteor.user() // works as usual 27 | Meteor.user(true) // gets data from mergebox 28 | Meteor.user("profile.name") // verifies if the fields are in MergeBox. If not, it retrieves the document from the database 29 | Meteor.user(['profile.name', 'email']) // verifies if the fields are in MergeBox. If not, it retrieves the document from the database 30 | ``` 31 | 32 | `Meteor.user()` will continue to return the user document as usual. If can satisfy your field requirements, it will do so from MergeBox. If it cannot, it will retrieve the data from the database as usual. 33 | 34 | ## Example 35 | 36 | In the example below, we are using `Meteor.user()` for a roles check to ensure that the user is not banned. Typically, this would require us to ping the database. However, with the userCache package, we can bypass that entire query, and all the processing that comes with it. By doing so, we improve performance and speed up response time. 37 | 38 | ```js 39 | Meteor.methods({ 40 | "posts/create": function (content) { 41 | var userDoc = Meteor.user(["profile.roles"]) 42 | 43 | var authorization = function () { 44 | return !!userDoc.profile.roles.includes("banned"); 45 | } 46 | 47 | if (authorization()) { 48 | Posts.insert({ 49 | user: Meteor.userId(), 50 | date: new Date(), 51 | content: content 52 | }) 53 | } 54 | } 55 | }) 56 | ``` 57 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "msavin:usercache", 3 | summary: "The one simple trick that can save you millions of database requests!", 4 | version: '1.0.0', 5 | git: "https://github.com/msavin/userCache.git", 6 | documentation: "README.md", 7 | }); 8 | 9 | Package.onUse(function(api) { 10 | api.addFiles("server.js", "server"); 11 | api.versionsFrom("1.5"); 12 | 13 | api.use([ 14 | 'ddp', 15 | ], 'server'); 16 | 17 | }); -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var _original = Meteor.user; 2 | 3 | var _getCache = function () { 4 | var result = undefined; 5 | var instance = DDP._CurrentMethodInvocation.get() || DDP._CurrentPublicationInvocation.get(); 6 | var isMeteor1_8 = Meteor.release.match('1.8') !== null; 7 | 8 | if (!instance.userId) { 9 | return result; 10 | } 11 | 12 | var connectionId = instance.connection.id; 13 | var connectionData = isMeteor1_8 ? Meteor.default_server.sessions.get(connectionId) : Meteor.default_server.sessions[connectionId]; 14 | var collectionViews = isMeteor1_8 ? connectionData.collectionViews.get('users').documents.get(instance.userId) : connectionData.collectionViews.users.documents[instance.userId]; 15 | var data = collectionViews && collectionViews.dataByKey || []; 16 | var source = isMeteor1_8 ? Array.from(data.entries()) : Object.keys(data); 17 | 18 | source.forEach(function (item) { 19 | if (!result) { 20 | result = {}; 21 | } 22 | var key = isMeteor1_8 ? item[0] : item; 23 | result[key] = isMeteor1_8 ? item[1][0].value : data[item][0].value; 24 | }); 25 | 26 | return result; 27 | } 28 | 29 | var _getField = function (doc, field) { 30 | field = field.split('.'); 31 | 32 | for (var i = 0; i < field.length; i++) { 33 | if (!doc[field[i]]) { 34 | return; 35 | } 36 | doc = doc[field[i]]; 37 | } 38 | 39 | return !!doc; 40 | } 41 | 42 | Meteor.user = function (input) { 43 | if (typeof input === "undefined") { 44 | return _original(); 45 | } 46 | 47 | if (input === true) { 48 | return _getCache() 49 | } 50 | 51 | 52 | if (typeof input === "string") { 53 | input = [input]; 54 | } 55 | 56 | if (typeof input === "object") { 57 | var cache = _getCache() 58 | var innocent = true; 59 | 60 | input.forEach(function (item) { 61 | if (typeof _getField(cache, item) === "undefined") { 62 | innocent = false; 63 | } 64 | }) 65 | 66 | if (innocent) { 67 | return cache; 68 | } else { 69 | return _original() 70 | } 71 | } 72 | } 73 | --------------------------------------------------------------------------------