Skip to content

Commit

Permalink
Update docs to reflect new extensions configuration setup
Browse files Browse the repository at this point in the history
  • Loading branch information
airhorns committed Jun 19, 2020
1 parent 6a410e9 commit e3b2b81
Show file tree
Hide file tree
Showing 16 changed files with 613 additions and 366 deletions.
11 changes: 8 additions & 3 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
### v3.0.0 (unreleased)

**Breaking changes:**
- Update GraphQL requirement to version 15, which supports a new `extensions` property where join-monster config lives. The config keys and values are unchanged, but they must be nested under an `extensions: { joinMonster: ... }}` property on the GraphQLObjectTypes and fields using join-monster. For more information, see the upgrade guide:

### v2.1.2 (May 25, 2020)
#### Fixed
#### Fixed
- Connections inside union fragments [#407](https://github.com/join-monster/join-monster/pull/407)

### v2.1.1 (Nov. 21, 2019)
#### Fixed
#### Fixed
- Updated vulnerable version of lodash (`eed0264`)

### v2.1.0 (Aug. 25, 2018)
Expand Down Expand Up @@ -134,7 +139,7 @@
// created_at: 'DESC',
// id: 'ASC'
//}

// you could also place a `where` at either
}
```
Expand Down
21 changes: 15 additions & 6 deletions docs/aggregation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ const Post = new GraphQLObjectType({
description: 'The number of comments on this post',
type: GraphQLInt,
// use a correlated subquery in a raw SQL expression to do things like aggregation
sqlExpr: postTable => `(SELECT count(*) FROM comments WHERE post_id = ${postTable}.id AND archived = FALSE)`
},
extensions: {
joinMonster: {
sqlExpr: postTable =>
`(SELECT count(*) FROM comments WHERE post_id = ${postTable}.id AND archived = FALSE)`
}
}
}
})
})
```
Expand Down Expand Up @@ -57,8 +62,13 @@ const Post = new GraphQLObjectType({
fields: () => ({
commentsWithoutJoin: {
type: new GraphQLList(SimpleComment),
sqlExpr: postTable => `(SELECT json_agg(comments) FROM comments WHERE comments.post_id = ${postTable}.id AND comments.archived = FALSE)`
},
extensions: {
joinMonster: {
sqlExpr: postTable =>
`(SELECT json_agg(comments) FROM comments WHERE comments.post_id = ${postTable}.id AND comments.archived = FALSE)`
}
}
}
})
})
```
Expand All @@ -76,7 +86,7 @@ This should work without any additional data munging if you're using `knex`, as
commentsWithoutJoin {
id
body
authorId
authorId
}
}
}
Expand All @@ -98,4 +108,3 @@ FROM accounts AS "user"
LEFT JOIN posts AS "posts" ON "user".id = "posts".author_id
WHERE "user".id = 2
```

66 changes: 51 additions & 15 deletions docs/arbitrary-depth.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ const Post = new GraphQLObjectType({
author: {
description: 'The user that created the post',
type: User,
sqlJoin: (postTable, userTable, args, context) => `${postTable}.author_id = ${userTable}.id`
extensions: {
joinMonster: {
sqlJoin: (postTable, userTable, args, context) =>
`${postTable}.author_id = ${userTable}.id`
}
}
}
})
})
Expand All @@ -31,12 +36,22 @@ const Comment = new GraphQLObjectType({
post: {
description: 'The post that the comment belongs to',
type: Post,
sqlJoin: (commentTable, postTable) => `${commentTable}.post_id = ${postTable}.id`
extensions: {
joinMonster: {
sqlJoin: (commentTable, postTable) =>
`${commentTable}.post_id = ${postTable}.id`
}
}
},
author: {
description: 'The user who wrote the comment',
type: User,
sqlJoin: (commentTable, userTable) => `${commentTable}.author_id = ${userTable}.id`
extensions: {
joinMonster: {
sqlJoin: (commentTable, userTable) =>
`${commentTable}.author_id = ${userTable}.id`
}
}
}
})
})
Expand All @@ -46,14 +61,22 @@ Now you can get the comments the user has written, the post on which each commen

```graphql
{
users {
id, email, fullName
users {
id
email
fullName
comments {
id, body
author { fullName }
id
body
author {
fullName
}
post {
id, body
author { fullName }
id
body
author {
fullName
}
}
}
}
Expand All @@ -71,11 +94,21 @@ const User = new GraphQLObjectType({
//...
posts: {
type: new GraphQLList(Post),
sqlJoin: (userTable, postTable, args) => `${userTable}.id = ${postTable}.author_id`
extensions: {
joinMonster: {
sqlJoin: (userTable, postTable, args) =>
`${userTable}.id = ${postTable}.author_id`
}
}
},
comments: {
type: new GraphQLList(Comment),
sqlJoin: (userTable, commentTable, args) => `${userTable}.id = ${commentTable}.author_id`
extensions: {
joinMonster: {
sqlJoin: (userTable, commentTable, args) =>
`${userTable}.id = ${commentTable}.author_id`
}
}
}
})
})
Expand All @@ -91,15 +124,18 @@ const Post = new GraphQLObjectType({
comments: {
description: 'The comments on this post',
type: new GraphQLList(Comment),
// the JOIN condition also checks that the comment is not archived
sqlJoin: (postTable, commentTable) => `${postTable}.id = ${commentTable}.post_id AND ${commentTable}.archived = FALSE`,
extensions: {
joinMonster: {
// the JOIN condition also checks that the comment is not archived
sqlJoin: (postTable, commentTable) =>
`${postTable}.id = ${commentTable}.post_id AND ${commentTable}.archived = FALSE`
}
}
}
})
})

```

Again, the data is all fetched in a single query thanks to `JOIN`s.
However, doing all these joins can be cumbersome on the database.
We can split it into two, or perhaps more, separate queries to reduce the number of joins in the next section.

30 changes: 17 additions & 13 deletions docs/batch-many-many.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ const User = new GraphQLObjectType({
following: {
description: 'Users that this user is following',
type: new GraphQLList(User),
// batching many-to-many is supported too
junction: {
sqlTable: 'relationships',
// this table has no primary key, but the combination of these two columns is unique
uniqueKey: [ 'follower_id', 'followee_id' ],
sqlBatch: {
// the matching column in the junction table
thisKey: 'follower_id',
// the column to match in the user table
parentKey: 'id',
// how to join the related table to the junction table
sqlJoin: (junctionTable, followeeTable) => `${junctionTable}.followee_id = ${followeeTable}.id`
extensions: {
joinMonster: {
// batching many-to-many is supported too
junction: {
sqlTable: 'relationships',
// this table has no primary key, but the combination of these two columns is unique
uniqueKey: ['follower_id', 'followee_id'],
sqlBatch: {
// the matching column in the junction table
thisKey: 'follower_id',
// the column to match in the user table
parentKey: 'id',
// how to join the related table to the junction table
sqlJoin: (junctionTable, followeeTable) =>
`${junctionTable}.followee_id = ${followeeTable}.id`
}
}
}
}
}
Expand All @@ -37,4 +42,3 @@ In addition to the changes made on the previous page, the plan now has 3 databas
![query-plan-3](img/query-plan-3.png)

Requests for the followees and for the comments are independent, and are sent concurrently.

27 changes: 15 additions & 12 deletions docs/batch-one-many.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ const Post = new GraphQLObjectType({
comments: {
description: 'The comments on this post',
type: new GraphQLList(Comment),
// instead of doing yet another JOIN, we'll get these comments in a separate batch
// sqlJoin: (postTable, commentTable) => `${postTable}.id = ${commentTable}.post_id AND ${commentTable}.archived = FALSE`,
sqlBatch: {
// which column to match up to the users
thisKey: 'post_id',
// the other column to compare to
parentKey: 'id'
},
// sqlBatch works with the `where` function too. get only non-archived comments
where: table => `${table}.archived = FALSE`
},
extensions: {
joinMonster: {
// instead of doing yet another JOIN, we'll get these comments in a separate batch
// sqlJoin: (postTable, commentTable) => `${postTable}.id = ${commentTable}.post_id AND ${commentTable}.archived = FALSE`,
sqlBatch: {
// which column to match up to the users
thisKey: 'post_id',
// the other column to compare to
parentKey: 'id'
},
// sqlBatch works with the `where` function too. get only non-archived comments
where: table => `${table}.archived = FALSE`
}
}
}
})
})
```
Expand Down Expand Up @@ -91,4 +95,3 @@ Two database queries are made regardless of the number of posts, another way to

Although this also works perfectly fine for a one-to-one relation, it is not recommended.
Not much is gained by batching on a one-to-one since using a simple `JOIN` would not burden the database greatly.

47 changes: 33 additions & 14 deletions docs/field-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ const User = new GraphQLObjectType({
},
email: {
type: GraphQLString,
// if the column name is different, it must be specified
sqlColumn: 'email_address'
extensions: {
joinMonster: {
// if the column name is different, it must be specified
sqlColumn: 'email_address'
}
}
},
idEncoded: {
description: 'The ID base-64 encoded',
type: GraphQLString,
sqlColumn: 'id',
extensions: {
joinMonster: {
sqlColumn: 'id'
}
},
// this field uses a sqlColumn and applies a resolver function on the value
// if a resolver is present, the `sqlColumn` MUST be specified even if it is the same name as the field
resolve: user => toBase64(user.id)
Expand All @@ -45,41 +53,52 @@ In the case of the `id` field, the `sqlColumn` was omitted. Since it has no reso

You can manipulate the data in your query without losing the benefit of batched requests.

Maybe your field(s) needs a SQL column to compute a value. If there isn't a simple one-to-one correspondence of columns to field, you can use `sqlDeps`. `sqlDeps` is an array of columns that will get retrieved if the GraphQL field is requested. These are exposed to your resolver, so you can write a `resolve` function to compute a value in JavaScript. For example, a `first_name` and `last_name` column can be *depended on* for a `fullName` field in your API.
Maybe your field(s) needs a SQL column to compute a value. If there isn't a simple one-to-one correspondence of columns to field, you can use `sqlDeps`. `sqlDeps` is an array of columns that will get retrieved if the GraphQL field is requested. These are exposed to your resolver, so you can write a `resolve` function to compute a value in JavaScript. For example, a `first_name` and `last_name` column can be _depended on_ for a `fullName` field in your API.

```javascript
const User = new GraphQLObjectType({
//...
fields: () => ({
fullName: {
description: 'A user\'s first and last name',
description: "A user's first and last name",
type: GraphQLString,
// perhaps there is no 1-to-1 mapping of field to column
// this field depends on multiple columns
sqlDeps: [ 'first_name', 'last_name' ],
extensions: {
joinMonster: {
// perhaps there is no 1-to-1 mapping of field to column
// this field depends on multiple columns
sqlDeps: ['first_name', 'last_name']
}
},
resolve: user => `${user.first_name} ${user.last_name}`
}
})
})
```

You can also do computed columns in the SQL itself with a *raw expression* using `sqlExpr`. This is a function that generated the expression. Its parameters are the table alias (generated automatically by joinMonster), the GraphQL arguments on that field, and a [context](/where/#adding-context) object.
You can also do computed columns in the SQL itself with a _raw expression_ using `sqlExpr`. This is a function that generated the expression. Its parameters are the table alias (generated automatically by joinMonster), the GraphQL arguments on that field, and a [context](/where/#adding-context) object.

```javascript
const User = new GraphQLObjectType({
//...
fields: () => ({
capitalizedLastName: {
type: GraphQLString,
// do a computed column in SQL with raw expression
sqlExpr: (table, args) => `UPPER(${table}.last_name)`
extensions: {
joinMonster: {
// do a computed column in SQL with raw expression
sqlExpr: (table, args) => `UPPER(${table}.last_name)`
}
}
},
fullNameAnotherWay: {
description: 'Another way we can get the full name.',
type: GraphQLString,
sqlExpr: table => `${table}.first_name || ' ' || ${table}.last_name`
},
extensions: {
joinMonster: {
sqlExpr: table => `${table}.first_name || ' ' || ${table}.last_name`
}
}
}
})
})
```

0 comments on commit e3b2b81

Please sign in to comment.