From ae989265cc472378aea98bf8f6771896e0a2a326 Mon Sep 17 00:00:00 2001 From: Theodor Diaconu Date: Fri, 1 Dec 2017 12:58:36 +0200 Subject: [PATCH] documentation rc 3 --- docs/api.md | 24 ++++++++++++++++++------ docs/caching_results.md | 9 +++++---- docs/denormalization.md | 15 ++++++++------- docs/global_exposure.md | 4 ++-- docs/hypernova.md | 7 ++++--- docs/introduction.md | 4 ++-- docs/linker_engine.md | 4 ++-- docs/linking_collections.md | 4 ++-- docs/named_queries.md | 4 ++-- docs/outside_meteor.md | 16 +++++++++------- docs/query_options.md | 4 ++-- docs/reducers.md | 4 ++-- docs/structure_and_patterns.md | 31 ++++++++++++++++++++++++------- 13 files changed, 82 insertions(+), 48 deletions(-) diff --git a/docs/api.md b/docs/api.md index e1dbc6b..1d2c753 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2,6 +2,17 @@ Use this as a cheatsheet after you have read the full documentation. + +- [Adding Links](#adding-links) +- [Adding Reducers](#adding-reducers) +- [Creating Named Queries](#creating-named-queries) +- [Exposing Named Queries](#exposing-named-queries) +- [Using Queries](#using-queries) +- [Caching Named Queries](#caching-named-queries) +- [Creating Global Queries](#creating-global-queries) +- [Exposing Global Queries](#exposing-global-queries) + + ### Adding Links ```js @@ -43,7 +54,7 @@ Collection.addReducers({ }) ``` -### Creating Named Query +### Creating Named Queries ```js Collection.createQuery('queryName', { @@ -60,7 +71,7 @@ Collection.createQuery('queryName', { }) ``` -### Exposing Named Query +### Exposing Named Queries ```js query.expose({ @@ -73,7 +84,7 @@ query.expose({ }) ``` -### Creating and Exposting Resolvers +### Creating and Exposing Resolvers ```js // both @@ -90,7 +101,7 @@ query.resolve(function (params) { }); ``` -### Using Query +### Using Queries ```js query.setParams({}) // extends current params @@ -134,7 +145,8 @@ query.cacheResults(new MemoryResultCacher({ })) ``` -#### Creating Global Query +#### Creating Global Queries + ```js Collection.createQuery({ $options, // Mongo Options {sort, limit, skip} @@ -147,7 +159,7 @@ Collection.createQuery({ }) ``` -#### Exposing Global Query +#### Exposing Global Queries ```js Collection.expose({ diff --git a/docs/caching_results.md b/docs/caching_results.md index 0d972a8..0c3f1ac 100644 --- a/docs/caching_results.md +++ b/docs/caching_results.md @@ -5,7 +5,7 @@ Ok, it's time for Grapher to take it to the next level. Let's cache our very hea Caching results only works for Named Queries. -Let's take an example +Let's take an example: ```js export default Meteor.users.createQuery('myFriendsEmails', { @@ -69,6 +69,7 @@ you may want to use `redis` for that. ```js import {BaseResultCacher} from 'meteor/cultofcoders:grapher'; +import {EJSON} from 'meteor/ejson'; /** * Redis Cacher @@ -115,7 +116,7 @@ myFriendsQuery.cacheResults(cacher); ## Caching Resolver Queries -As you may have guessed, this works with resolver queries as well, in their case, instead of the actual query, +As you may have guessed, this works with [resolver queries](named_queries.md#resolvers) as well, in their case, instead of the actual query, we pass as `query` parameter an interface containing `fetch()`. And your cacher, will never receive `cursorCount` inside the `fetchables` object in this case. @@ -127,12 +128,12 @@ Therefore you can use the same paradigms for your cache for resolver queries as Unfortunately, if you want to invalidate your cache you can do it yourself manually, as this is not implemented, but since you can hook in your own cacher, you can do whatever you want. -## [Conclusion] +## Conclusion If you have very heavy and frequent requests to the database, or any type of requests (resolver queries) you can think about using a cache, with very few lines of code. -#### [Continue Reading](global_exposure.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](global_exposure.md) or [Back to Table of Contents](index.md) diff --git a/docs/denormalization.md b/docs/denormalization.md index 84159ba..913afec 100644 --- a/docs/denormalization.md +++ b/docs/denormalization.md @@ -11,7 +11,7 @@ You may need denormalization in such cases where: A simple example, a user has an avatar that is stored in the image collection: ```js -// Assuming Images of schema: {_id, path, smallThumb, createdAt} +// Assuming Images of schema: {_id, path, smallThumbPath, createdAt} import Images from '...'; Meteor.users.addLinks({ @@ -24,7 +24,7 @@ Meteor.users.addLinks({ // what fields to cache from Images, this only works for fields and not links body: { path: 1, - smallThumb: 1, + smallThumbPath: 1, } } } @@ -83,14 +83,14 @@ Denormalization works with any type of links `one`, `many`, `meta` whether they We previously tackled the case where we needed `$postFilters` or `$postFilter` to retrieve filtered data. For example, let's say we want to retrieve only the users that have reviewed a book of a certain type, -and inside `users` collection we have a `reviewedBoos` link. +and inside `users` collection we have a `reviewedBooks` link. ```js Meteor.users.addLinks({ 'reviewedBooks': { type: 'many', collection: Books, - field: 'reviewedBooksIds', + field: 'reviewedBookIds', denormalize: { body: { type: 1, @@ -142,12 +142,13 @@ If you want to use deep filters, it will not work with denormalized cache, you c } ``` -Because if you put `$filters: {}` inside the body of the cache, it will regard it as a foreign field, and it will fetch the linked Collection for it. +Because if you put `$filters: {}` inside the body of the cache, it will regard it as a foreign field, and it will +fetch the linked Collection for it, falling back to the original behavior. A current limitation for denormalized meta links, is that we will no longer be able to store the `$metadata` inside the nested object, because that would require additional fetching of the link storage if we are querying the graph from the inversed side. -## [Conclusion] +## Conclusion Using denormalization can enable you to do wonderful things inside NoSQL, but also be careful because they come with a cost, that may not be very noticeable in the beginning. But can also dramatically improve performance at the same time. @@ -155,4 +156,4 @@ that may not be very noticeable in the beginning. But can also dramatically impr I suggest that they should be used to cache things that rarely change such as an user's avatar, or when you need to do powerful and frequent searches, that otherwise would have consumed more resources. -#### [Continue Reading](caching_results.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](caching_results.md) or [Back to Table of Contents](index.md) diff --git a/docs/global_exposure.md b/docs/global_exposure.md index b68f3f0..902e069 100644 --- a/docs/global_exposure.md +++ b/docs/global_exposure.md @@ -335,9 +335,9 @@ This will allow requests like: } ``` -## [Conclusion] +## Conclusion The global queries are a very powerful tool to expose your full database, but unlike `Named Queries` they do not benefit of `caching`. -#### [Continue Reading](structure_and_patterns.md) or [Back to Table of Contents](table_of_contents.md) \ No newline at end of file +## [Continue Reading](structure_and_patterns.md) or [Back to Table of Contents](index.md) \ No newline at end of file diff --git a/docs/hypernova.md b/docs/hypernova.md index be38ca0..357f971 100644 --- a/docs/hypernova.md +++ b/docs/hypernova.md @@ -73,8 +73,9 @@ propper objects. Not only it makes 5 requests instead of 131, but it smartly re-uses categories and authors at each collection node, meaning you will have less bandwidth consumed. -Making it more efficient in terms of bandwidth than SQL. Yes, you read that correct: +Making it more efficient in terms of bandwidth than SQL or other relational databases. Yes, you read that correct. +Example: ```js { posts: { @@ -87,13 +88,13 @@ Making it more efficient in terms of bandwidth than SQL. Yes, you read that corr Let's assume we have 100 posts, and the total number of categories is like 4. Hypernova does 2 requests to the database, and fetches 100 posts, and 4 categories. If you would have used `JOIN` functionality in SQL, you would have received -categories for each post. +the categories for each post. Now you understand why this is a revolution for MongoDB and Meteor. Keep in mind that Hypernova is only used for static queries. For reactive queries, we still rely on the recursive fetching. -#### [Continue Reading](denormalization.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](denormalization.md) or [Back to Table of Contents](index.md) diff --git a/docs/introduction.md b/docs/introduction.md index 6990625..d8b558a 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -208,9 +208,9 @@ export default Posts.createQuery({...}, { }); ``` -## [Conclusion] +## Conclusion This is the end of our introduction. As we can see, we can make queries modular and this already gives us a big benefit. By abstracting them into their own modules we can keep our methods neat and clean. -#### [Continue Reading](linking_collections.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](linking_collections.md) or [Back to Table of Contents](index.md) diff --git a/docs/linker_engine.md b/docs/linker_engine.md index a453b1a..ad14116 100644 --- a/docs/linker_engine.md +++ b/docs/linker_engine.md @@ -162,12 +162,12 @@ userGroupsLink.metadata([groupId1, groupId2], { Updating metadata only works with strings or objects that contain `_id`, and it works from both sides. -## [Conclusion] +## Conclusion By using this Programatic API to set your links instead of relying on updates, it makes your code much simpler to read, and makes schema migration easier in the future. -#### [Continue Reading](query_options.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](query_options.md) or [Back to Table of Contents](index.md) diff --git a/docs/linking_collections.md b/docs/linking_collections.md index d4d6a35..4001f1b 100644 --- a/docs/linking_collections.md +++ b/docs/linking_collections.md @@ -450,12 +450,12 @@ eliminate large and complex documents by abstracting them into collections. In the future, this limitation may change, but for now you can work around this and keep your code elegant. -## [Conclusion] +## Conclusion Using these simple techniques, you can create a beautiful database schemas inside MongoDB that are relational and very simple to fetch, you will eliminate almost all your boilerplate code around this and allows you to focus on more important things. -#### [Continue Reading](linker_engine.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](linker_engine.md) or [Back to Table of Contents](index.md) diff --git a/docs/named_queries.md b/docs/named_queries.md index 5de6d62..a1bfd3a 100644 --- a/docs/named_queries.md +++ b/docs/named_queries.md @@ -391,10 +391,10 @@ query.expose({ }) ``` -## [Conclusion] +## Conclusion We can now safely expose our queries to the client, and the client can use it in a simple and uniform way. By this stage we already understand how powerful Grapher really is, but it still has some tricks up it's sleeve. -#### [Continue Reading](hypernova.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](hypernova.md) or [Back to Table of Contents](index.md) diff --git a/docs/outside_meteor.md b/docs/outside_meteor.md index ec559ec..4d424f3 100644 --- a/docs/outside_meteor.md +++ b/docs/outside_meteor.md @@ -2,7 +2,7 @@ If you like Grapher, or if `like` is an understatement, you can use it in any other language/medium. -You can expose it as an HTTP API, or a DDP API (as a Meteor method) for example, because +You can expose it as an `HTTP API`, or via `DDP` (as a meteor method) for example, because in React Native you have ways to connect to Meteor with: https://www.npmjs.com/package/react-native-meteor#meteor-collections @@ -52,9 +52,10 @@ grapherRoutes.route('/grapher', function (req, res) { try { const data = actualQuery.fetch({ // the userId (User Identification) that hits the firewalls - // user id can be anything, an API key maybe, not only a 'string' - // it's up to you and your firewalls to decide - userId: 'XXX' + // user id can be anything, an API KEY, an Object, you and your firewalls decide + userId: 'XXX', + // you can specify other fields, because this is the context of the firewall + // whatever you define here, it can be accessed with `this` }); res.statusCode = 200; @@ -75,7 +76,8 @@ in EJSON format. If `EJSON` is not available in your language, you can parse it If you are in the JavaScript world: https://www.npmjs.com/package/ejson -If you want to use Meteor's Methods as an HTTP API to also handle method calls, take a look here: https://github.com/cult-of-coders/fusion +If you want to use Meteor's Methods as an `HTTP API` to also handle mutations, +take a look here: https://github.com/cult-of-coders/fusion And more closely: https://github.com/cult-of-coders/fusion/blob/7ec5cd50c3a471c0bdd65c9fa482124c149dc243/fusion/server/route.js @@ -107,8 +109,8 @@ Meteor.methods({ }) ``` -## [Conclusion] +## Conclusion Nothing stops you from using Grapher outside Meteor! -#### [Continue Reading](api.md) or [Back to Table of Contents](table_of_contents.md) \ No newline at end of file +## [Continue Reading](api.md) or [Back to Table of Contents](index.md) \ No newline at end of file diff --git a/docs/query_options.md b/docs/query_options.md index 552f887..e46d3d9 100644 --- a/docs/query_options.md +++ b/docs/query_options.md @@ -368,12 +368,12 @@ const query = postLists.clone({ When we cross to the client-side domain we need to be very wary of these type of injections. -## [Conclusion] +## Conclusion Query is a very powerful tool, very flexible, it allows us to do very complex things that would have taken us a lot of time to do otherwise. -#### [Continue Reading](reducers.md) or [Back to Table of Contents](table_of_contents.md) +## [Continue Reading](reducers.md) or [Back to Table of Contents](index.md) diff --git a/docs/reducers.md b/docs/reducers.md index 4fb119d..1f0f1e5 100644 --- a/docs/reducers.md +++ b/docs/reducers.md @@ -190,9 +190,9 @@ Projects.addReducers({ If you want to filter reducers you can use `$postFilters` or `$postFilter` special functions. -## [Conclusion] +## Conclusion Reducers are a neat way to remove boilerplate from your code, especially for our infamous `emails[0].address`, inside `Meteor.users` collection, check if you can figure out how to reduce it! -#### [Continue Reading](named_queries.md) or [Back to Table of Contents](table_of_contents.md) \ No newline at end of file +## [Continue Reading](named_queries.md) or [Back to Table of Contents](index.md) \ No newline at end of file diff --git a/docs/structure_and_patterns.md b/docs/structure_and_patterns.md index 7fc03fa..f9eecbd 100644 --- a/docs/structure_and_patterns.md +++ b/docs/structure_and_patterns.md @@ -21,14 +21,16 @@ We suggest you read: http://www.meteor-tuts.com/chapters/3/persistence-layer.htm If you respect the patterns above you will avoid having the most common pitfalls with Grapher: **Reducer/Links not working?** + Make sure they are imported in the environment you use them client/server. -**My link is not saved in the database** +**My link is not saved in the database?** + Make sure you added it correctly to your SimpleSchema, if you have that. ## Fragments -You will find yourself requiring often same fields for Users, such as email, fullName, and maybe avatar. +You will find yourself requiring often same fields for users, such as `email`, `fullName`, and maybe `avatar`. For that let's create some fragments: ```js @@ -63,7 +65,8 @@ Invoices.createQuery({ You can also compose certain fragments: ```js -import compose from 'meteor/cultofcoders:grapher'; +import {compose} from 'meteor/cultofcoders:grapher'; + import { UserPublicFragment, UserBillingFragment, @@ -81,9 +84,9 @@ Invoices.createQuery({ }) ``` -Compose uses a deep extension, so it works how you expected to work, especially if some fragments have shared bodies. +`compose()` uses deep extension, so it works how you expect it to work, especially if some fragments have shared bodies. -Do not use special properties inside Fragments, such as `$filters`, `$options`, etc. +Do not use special properties inside fragments, such as `$filters`, `$options`, etc. ## Scaling Reactivity @@ -104,8 +107,22 @@ export default Messages.createQuery('messagesForThread', { }) ``` -## [Conclusion] +Or, if you don't want to expose that, embody the `$filter()` server-side: + +```js +// query.expose.js +query.expose({ + embody: { + $filter({options, filters, params}) { + filters.threadId = params.threadId; + options.namespace = `thread::${params.threadId}`; + } + } +}) +``` + +## Conclusion Using some simple techniques we can make our code much easier to read, and we can make use of a scalable data graph using `redis-oplog` -#### [Continue Reading](outside_meteor.md) or [Back to Table of Contents](table_of_contents.md) \ No newline at end of file +## [Continue Reading](outside_meteor.md) or [Back to Table of Contents](index.md) \ No newline at end of file