mirror of
https://github.com/vale981/grapher
synced 2025-03-04 17:11:38 -05:00
documentation rc 3
This commit is contained in:
parent
c88e2a9f36
commit
ae989265cc
13 changed files with 82 additions and 48 deletions
24
docs/api.md
24
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({
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
## [Continue Reading](structure_and_patterns.md) or [Back to Table of Contents](index.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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
## [Continue Reading](api.md) or [Back to Table of Contents](index.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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
## [Continue Reading](named_queries.md) or [Back to Table of Contents](index.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)
|
||||
## [Continue Reading](outside_meteor.md) or [Back to Table of Contents](index.md)
|
Loading…
Add table
Reference in a new issue