Skip to content

Commit 1279957

Browse files
authored
Feature 28: Connection modes (#29)
* Document connection modes * Implementation * Test for connectionQueryModes * Add connectionQueryMode to setup doc * Bugfix * Fix typo
1 parent fdc0ad8 commit 1279957

File tree

11 files changed

+272
-18
lines changed

11 files changed

+272
-18
lines changed

dist/vuex-orm-graphql.esm.js

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9861,6 +9861,29 @@ var Schema = /** @class */ (function () {
98619861
this.getType('Query').fields.forEach(function (f) { return _this.queries.set(f.name, f); });
98629862
this.getType('Mutation').fields.forEach(function (f) { return _this.mutations.set(f.name, f); });
98639863
}
9864+
Schema.prototype.determineQueryMode = function () {
9865+
var _this = this;
9866+
var connection = null;
9867+
this.queries.forEach(function (query) {
9868+
if (query.type.name && query.type.name.endsWith('TypeConnection')) {
9869+
connection = _this.getType(query.type.name);
9870+
return false; // break
9871+
}
9872+
return true;
9873+
});
9874+
if (!connection) {
9875+
throw new Error("Can't determine the connection mode due to the fact that here are no connection types in the schema. Please set the connectionQueryMode via Vuex-ORM-GraphQL options!");
9876+
}
9877+
if (connection.fields.find(function (f) { return f.name === 'nodes'; })) {
9878+
return 'nodes';
9879+
}
9880+
else if (connection.fields.find(function (f) { return f.name === 'edges'; })) {
9881+
return 'edges';
9882+
}
9883+
else {
9884+
return 'plain';
9885+
}
9886+
};
98649887
Schema.prototype.getType = function (name) {
98659888
name = upcaseFirstLetter(name);
98669889
var type = this.types.get(name);
@@ -9881,7 +9904,7 @@ var Schema = /** @class */ (function () {
98819904
return query;
98829905
};
98839906
Schema.prototype.returnsConnection = function (field) {
9884-
return field.type.name.endsWith('TypeConnection');
9907+
return (field.type.name && field.type.name.endsWith('TypeConnection'));
98859908
};
98869909
return Schema;
98879910
}());
@@ -9947,6 +9970,10 @@ var Context = /** @class */ (function () {
99479970
* @type {boolean}
99489971
*/
99499972
this.debugMode = false;
9973+
/**
9974+
* Defines how to query connections. 'auto' | 'nodes' | 'edges' | 'plain'
9975+
*/
9976+
this.connectionQueryMode = 'auto';
99509977
this.components = components;
99519978
this.options = options;
99529979
this.database = options.database;
@@ -9989,6 +10016,12 @@ var Context = /** @class */ (function () {
998910016
case 0:
999010017
if (!!this.schema) return [3 /*break*/, 2];
999110018
this.logger.log('Fetching GraphQL Schema initially ...');
10019+
if (this.options.connectionQueryMode) {
10020+
this.connectionQueryMode = this.options.connectionQueryMode;
10021+
}
10022+
else {
10023+
this.connectionQueryMode = 'auto';
10024+
}
999210025
context = {
999310026
headers: { 'X-GraphQL-Introspection-Query': 'true' }
999410027
};
@@ -9999,7 +10032,7 @@ var Context = /** @class */ (function () {
999910032
this.logger.log('GraphQL Schema successful fetched', result);
1000010033
this.logger.log('Starting to process the schema ...');
1000110034
this.processSchema();
10002-
this.logger.log('Schema procession done ...');
10035+
this.logger.log('Schema procession done!');
1000310036
_a.label = 2;
1000410037
case 2: return [2 /*return*/, this.schema];
1000510038
}
@@ -10028,6 +10061,14 @@ var Context = /** @class */ (function () {
1002810061
}
1002910062
});
1003010063
});
10064+
if (this.connectionQueryMode === 'auto') {
10065+
this.connectionQueryMode = this.schema.determineQueryMode();
10066+
this.logger.log("Connection Query Mode is " + this.connectionQueryMode + " by automatic detection");
10067+
}
10068+
else {
10069+
console.log('---> Config!');
10070+
this.logger.log("Connection Query Mode is " + this.connectionQueryMode + " by config");
10071+
}
1003110072
};
1003210073
/**
1003310074
* Returns a model from the model collection by it's name
@@ -10092,7 +10133,16 @@ var QueryBuilder = /** @class */ (function () {
1009210133
var params = this.buildArguments(model, args, false, filter, allowIdFields);
1009310134
var fields = "\n " + model.getQueryFields().join(' ') + "\n " + this.buildRelationsQuery(model, ignoreRelations) + "\n ";
1009410135
if (multiple) {
10095-
return "\n " + (name ? name : model.pluralName) + params + " {\n nodes {\n " + fields + "\n }\n }\n ";
10136+
var header = "" + (name ? name : model.pluralName) + params;
10137+
if (context.connectionQueryMode === 'nodes') {
10138+
return "\n " + header + " {\n nodes {\n " + fields + "\n }\n }\n ";
10139+
}
10140+
else if (context.connectionQueryMode === 'edges') {
10141+
return "\n " + header + " {\n edges {\n node {\n " + fields + "\n }\n }\n }\n ";
10142+
}
10143+
else {
10144+
return "\n " + header + " {\n " + fields + "\n }\n ";
10145+
}
1009610146
}
1009710147
else {
1009810148
return "\n " + (name ? name : model.singularName) + params + " {\n " + fields + "\n }\n ";
@@ -10219,7 +10269,7 @@ var QueryBuilder = /** @class */ (function () {
1021910269
var context = Context.getInstance();
1022010270
var field = model.fields.get(key);
1022110271
var schemaField = context.schema.getType(model.singularName).fields.find(function (f) { return f.name === key; });
10222-
if (schemaField) {
10272+
if (schemaField && schemaField.type.name) {
1022310273
return schemaField.type.name;
1022410274
}
1022510275
else {

docs/.vuepress/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ themeConfig:
2525
- /guide/persist/
2626
- /guide/push/
2727
- /guide/destroy/
28+
- /guide/connection-mode/
2829
- /guide/custom-queries/
2930
- /guide/relationships/
3031
- /guide/eager-loading/

docs/guide/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ Vuex-ORM-GraphQL works with the [Apollo Dev Tools](https://chrome.google.com/web
9595
:::
9696

9797

98+
## Connection Mode
99+
100+
It seems that there are several standards within the GraphQL community how connections (fields that returns multiple
101+
records) are designed. Some do this via a `nodes` field, some via a `edges { nodes }` query and some do neither of them.
102+
Vuex-ORM-GraphQL tries to be flexible and supports all of them, but the example queries in the documentation work with
103+
the `nodes` query, don't be irritated. You'll find [more details here](/guide/connection-mode).
104+
105+
98106
## License
99107

100108
Vuex-ORM-GraphQL is open-sourced software licensed under the

docs/guide/connection-mode/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Connection Mode
2+
3+
[[toc]]
4+
5+
It seems that there are several standards within the GraphQL community how connections (fields that returns multiple
6+
records) are designed. Some do this via a `nodes` field, some via a `edges { nodes }` query and some do neither of them.
7+
Vuex-ORM-GraphQL tries to be flexible and supports all of them, but the example queries in the documentation work with
8+
the `nodes` query, don't be irritated.
9+
10+
11+
## Automatic detection
12+
13+
The plugin will try to detect automatically which mode should be used by analyzing the GraphQL Schema. In the best
14+
case you don't have to bother with this at all.
15+
16+
17+
## Manual setting
18+
19+
In rare cases the automatic detection might fail or report the wrong mode. In this case, you can manually set the
20+
`connectionQueryMode` config param to either `auto` (default), `nodes`, `edges`, `plain`. The modes and the resulting
21+
queries are explained in the next sections.
22+
23+
24+
## Mode 1: `nodes`
25+
26+
This is the preferred mode and used for the example queries in this documentation. Setting the connection mode to
27+
`nodes` (or letting the plugin auto detect this mode) will lead to the following query when calling `User.fetch()`:
28+
29+
```
30+
query Users {
31+
users {
32+
nodes {
33+
id
34+
email
35+
name
36+
}
37+
}
38+
}
39+
```
40+
41+
42+
## Mode 2: `edges`
43+
44+
This mode uses a `edges` not to query the edge an then query the `node` within that edge:
45+
46+
```
47+
query Users {
48+
users {
49+
edges {
50+
node {
51+
id
52+
email
53+
name
54+
}
55+
}
56+
}
57+
}
58+
```
59+
60+
61+
## Mode 3: `plain`
62+
63+
The third mode is the less preferred one due to the lack of meta information. In this case we just plain pass the field
64+
queries:
65+
66+
```
67+
query Users {
68+
users {
69+
id
70+
email
71+
name
72+
}
73+
}
74+
```

docs/guide/setup/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ These are the possible options to pass when calling `VuexORM.use()`:
3535
- `headers` (optional, default: `{}`) HTTP Headers. See [apollo-link-http])(https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-http#options)
3636
- `credentials` (optional, default: `same-origin`) Credentials Policy. See [apollo-link-http])(https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-http#options)
3737
- `useGETForQueries` (optional, default: `false`) Use GET for queries (not for mutations). See [apollo-link-http])(https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-http#options)
38+
- `connectionQueryMode` (optional, default: `auto`). One of `auto | nodes | edges | plain`. See [Connection Mode](/guide/connection-mode)
3839

3940
More options will come in future releases.
4041

src/common/context.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ export default class Context {
9999
*/
100100
public schema: Schema | undefined;
101101

102+
/**
103+
* Defines how to query connections. 'auto' | 'nodes' | 'edges' | 'plain'
104+
*/
105+
public connectionQueryMode: string = 'auto';
106+
102107
/**
103108
* Private constructor, called by the setup method
104109
*
@@ -153,6 +158,12 @@ export default class Context {
153158
if (!this.schema) {
154159
this.logger.log('Fetching GraphQL Schema initially ...');
155160

161+
if (this.options.connectionQueryMode) {
162+
this.connectionQueryMode = this.options.connectionQueryMode;
163+
} else {
164+
this.connectionQueryMode = 'auto';
165+
}
166+
156167
// We send a custom header along with the request. This is required for our test suite to mock the schema request.
157168
const context = {
158169
headers: { 'X-GraphQL-Introspection-Query': 'true' }
@@ -162,9 +173,10 @@ export default class Context {
162173
this.schema = new Schema(result.data.__schema);
163174

164175
this.logger.log('GraphQL Schema successful fetched', result);
176+
165177
this.logger.log('Starting to process the schema ...');
166178
this.processSchema();
167-
this.logger.log('Schema procession done ...');
179+
this.logger.log('Schema procession done!');
168180
}
169181

170182
return this.schema;
@@ -193,6 +205,14 @@ export default class Context {
193205
}
194206
});
195207
});
208+
209+
if (this.connectionQueryMode === 'auto') {
210+
this.connectionQueryMode = this.schema!.determineQueryMode();
211+
this.logger.log(`Connection Query Mode is ${this.connectionQueryMode} by automatic detection`);
212+
} else {
213+
console.log('---> Config!');
214+
this.logger.log(`Connection Query Mode is ${this.connectionQueryMode} by config`);
215+
}
196216
}
197217

198218
/**

src/graphql/query-builder.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,33 @@ export default class QueryBuilder {
4242
`;
4343

4444
if (multiple) {
45-
return `
46-
${name ? name : model.pluralName}${params} {
47-
nodes {
45+
const header: string = `${name ? name : model.pluralName}${params}`;
46+
47+
if (context.connectionQueryMode === 'nodes') {
48+
return `
49+
${header} {
50+
nodes {
51+
${fields}
52+
}
53+
}
54+
`;
55+
} else if (context.connectionQueryMode === 'edges') {
56+
return `
57+
${header} {
58+
edges {
59+
node {
60+
${fields}
61+
}
62+
}
63+
}
64+
`;
65+
} else {
66+
return `
67+
${header} {
4868
${fields}
4969
}
50-
}
51-
`;
70+
`;
71+
}
5272
} else {
5373
return `
5474
${name ? name : model.singularName}${params} {
@@ -190,7 +210,7 @@ export default class QueryBuilder {
190210

191211
const schemaField = context.schema!.getType(model.singularName).fields!.find(f => f.name === key);
192212

193-
if (schemaField) {
213+
if (schemaField && schemaField.type.name) {
194214
return schemaField.type.name;
195215
} else {
196216
if (field instanceof context.components.String) {

src/graphql/schema.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@ export default class Schema {
1919
this.getType('Mutation').fields!.forEach(f => this.mutations.set(f.name, f));
2020
}
2121

22+
public determineQueryMode (): string {
23+
let connection: GraphQLType | null = null;
24+
25+
this.queries.forEach((query) => {
26+
if (query.type.name && query.type.name.endsWith('TypeConnection')) {
27+
connection = this.getType(query.type.name);
28+
return false; // break
29+
}
30+
return true;
31+
});
32+
33+
if (!connection) {
34+
throw new Error("Can't determine the connection mode due to the fact that here are no connection types in the schema. Please set the connectionQueryMode via Vuex-ORM-GraphQL options!");
35+
}
36+
37+
if (connection!.fields!.find(f => f.name === 'nodes')) {
38+
return 'nodes';
39+
} else if (connection!.fields!.find(f => f.name === 'edges')) {
40+
return 'edges';
41+
} else {
42+
return 'plain';
43+
}
44+
}
45+
2246
public getType (name: string): GraphQLType {
2347
name = upcaseFirstLetter(name);
2448
const type = this.types.get(name);
@@ -44,7 +68,7 @@ export default class Schema {
4468
return query;
4569
}
4670

47-
public returnsConnection (field: GraphQLField) {
48-
return field.type.name.endsWith('TypeConnection');
71+
public returnsConnection (field: GraphQLField): boolean {
72+
return (field.type.name && field.type.name.endsWith('TypeConnection')) as boolean;
4973
}
5074
}

src/support/interfaces.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface Options {
1111
credentials?: string;
1212
useGETForQueries?: boolean;
1313
debug?: boolean;
14+
connectionQueryMode?: string;
1415
}
1516

1617
export interface ActionParams {
@@ -58,7 +59,7 @@ export interface GraphQLField {
5859

5960
export interface GraphQLTypeDefinition {
6061
kind: string;
61-
name: string;
62+
name?: string;
6263
}
6364

6465
export interface GraphQLSchema {

0 commit comments

Comments
 (0)