mirror of
https://github.com/vale981/grapher
synced 2025-03-04 09:01:40 -05:00
Scoped query docs & some improvements
This commit is contained in:
parent
70fd03a743
commit
e72b206ae1
6 changed files with 114 additions and 5 deletions
89
docs/named_queries.md
Normal file → Executable file
89
docs/named_queries.md
Normal file → Executable file
|
@ -426,6 +426,95 @@ query.expose({
|
|||
})
|
||||
```
|
||||
|
||||
## Scoped publications
|
||||
|
||||
Scoped publications add a scope (context) to their published documents with the goal of client being able to distinguish between documents of different publications or different grapher paths in the same publication.
|
||||
|
||||
Problems often arise when using only server-side filtering.
|
||||
|
||||
Consider the following situation:
|
||||
|
||||
```js
|
||||
// the query
|
||||
const usersQuery = Users.createQuery('getUsers', {
|
||||
name: 1,
|
||||
friends: {
|
||||
name: 1,
|
||||
},
|
||||
}, {
|
||||
scoped: true,
|
||||
});
|
||||
|
||||
// server-side exposure
|
||||
usersQuery.expose({
|
||||
embody: {
|
||||
$filter({filters, params}) {
|
||||
filters.name = params.name;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// links
|
||||
Users.addLinks({
|
||||
friends: {
|
||||
collection: Users,
|
||||
field: 'friendIds',
|
||||
type: 'many'
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Notice that `friends` is a link from Users to Users collection. Also, we have server-side filtering (see exposure).
|
||||
On the client, we want to fetch reactively one user by name, but we are going to get all of his friends, too, and that is because of the `friends` link.
|
||||
```js
|
||||
// querying for user John
|
||||
withQuery(props => {
|
||||
return usersQuery.clone({
|
||||
name: 'John',
|
||||
});
|
||||
}, {
|
||||
reactive: true,
|
||||
})(SomeComponent);
|
||||
```
|
||||
|
||||
Client receives queried user (John) and all of his friends into the local Users collection.
|
||||
By passing `{scoped: true}` query parameter to the `createQuery()`, client-side recursive fetching is now able to distinguish between queried user and his friends.
|
||||
|
||||
### Technical details
|
||||
Continuing on the example above, there are two pieces on how server and client achieve this functionality.
|
||||
|
||||
#### Subscription scope
|
||||
Each subscription adds `_sub_<subscriptionId>` field to its documents. For example, a User document could look like this:
|
||||
```
|
||||
{
|
||||
name: 'John',
|
||||
_sub_1: 1,
|
||||
_sub_2: 1
|
||||
}
|
||||
```
|
||||
This way we ensure that there is no mixup between the subscriptions (i.e. between two reactive queries on the client).
|
||||
|
||||
#### Query path scope
|
||||
Now suppose Alice is John's friend. Both Alice and John would have the same `_sub_<id>` field for our example query and we would get both instead of only John.
|
||||
This part is solved by adding "query path" field to the docs, in format `_query_path_<namespace>` where namespace is path constructed from collection name and link names, for example:
|
||||
```
|
||||
{
|
||||
name: 'John',
|
||||
_sub_1: 1,
|
||||
_query_path_users: 1,
|
||||
},
|
||||
{
|
||||
name: 'Alice',
|
||||
_sub_1: 1,
|
||||
// deeper nesting than John's
|
||||
_query_path_users_friends: 1
|
||||
}
|
||||
```
|
||||
|
||||
where Alice has namespace equal to `users_friends` and client-side recursive fetching can now distinguish between the documents that should be returned as a query result (John) and as a `friends` link results for John (which is Alice).
|
||||
|
||||
By adding query path field into the documents, we ensure that there is no mixup between the documents in the same reactive query (i.e. subscription).
|
||||
|
||||
## Conclusion
|
||||
|
||||
We can now safely expose our queries to the client, and the client can use it in a simple and uniform way.
|
||||
|
|
|
@ -5,6 +5,9 @@ const userListScoped = createQuery('userListScoped', {
|
|||
name: 1,
|
||||
friends: {
|
||||
name: 1
|
||||
},
|
||||
subordinates: {
|
||||
name: 1,
|
||||
}
|
||||
}
|
||||
}, {
|
||||
|
|
|
@ -157,7 +157,7 @@ describe('Named Query', function() {
|
|||
handle.stop();
|
||||
|
||||
assert.equal(data.length, 1);
|
||||
// User 3 has users 0,1,2 as friends
|
||||
// User 3 has users 0,1,2 as friends and user 2 as subordinate
|
||||
const [user3] = data;
|
||||
assert.equal(user3.friends.length, 3);
|
||||
|
||||
|
@ -167,17 +167,26 @@ describe('Named Query', function() {
|
|||
|
||||
const scopeField = `_sub_${handle.subscriptionId}`;
|
||||
const rootQueryPathField = '_query_path_users';
|
||||
const nestedQueryPathField = '_query_path_users_users';
|
||||
const friendsQueryPathField = '_query_path_users_friends';
|
||||
const adversaryQueryPathField = '_query_path_users_subordinates';
|
||||
Object.entries(docMap).forEach(([userId, userDoc]) => {
|
||||
const isRoot = userId === user3._id;
|
||||
assert.equal(userDoc[scopeField], 1);
|
||||
if (isRoot) {
|
||||
assert.equal(userDoc[rootQueryPathField], 1);
|
||||
assert.isTrue(!(nestedQueryPathField in userDoc));
|
||||
assert.isTrue(!(friendsQueryPathField in userDoc));
|
||||
assert.isTrue(!(adversaryQueryPathField in userDoc));
|
||||
}
|
||||
else {
|
||||
assert.equal(userDoc[nestedQueryPathField], 1);
|
||||
assert.equal(userDoc[friendsQueryPathField], 1);
|
||||
assert.isTrue(!(rootQueryPathField in userDoc));
|
||||
|
||||
if (userDoc.name === 'User - 2') {
|
||||
assert.equal(userDoc[adversaryQueryPathField], 1);
|
||||
}
|
||||
else {
|
||||
assert.isTrue(!(adversaryQueryPathField in userDoc));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -109,7 +109,9 @@ export function getNodeNamespace(node) {
|
|||
const parts = [];
|
||||
let n = node;
|
||||
while (n) {
|
||||
parts.push(n.collection._name);
|
||||
const name = n.linker ? n.linker.linkName : n.collection._name;
|
||||
parts.push(name);
|
||||
// console.log('linker', node.linker ? node.linker.linkName : node.collection._name);
|
||||
n = n.parent;
|
||||
}
|
||||
return parts.reverse().join('_');
|
||||
|
|
|
@ -90,6 +90,7 @@ _.range(USERS).forEach(idx => {
|
|||
const id = Users.insert({
|
||||
name: `User - ${idx}`,
|
||||
friendIds,
|
||||
subordinateIds: idx === 3 ? [friendIds[2]] : [],
|
||||
});
|
||||
|
||||
friendIds.push(id);
|
||||
|
|
|
@ -6,4 +6,9 @@ Users.addLinks({
|
|||
field: 'friendIds',
|
||||
type: 'many'
|
||||
},
|
||||
subordinates: {
|
||||
collection: Users,
|
||||
field: 'subordinateIds',
|
||||
type: 'many'
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue