Skip to content

Feature 28: Connection modes #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions dist/vuex-orm-graphql.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -9861,6 +9861,29 @@ var Schema = /** @class */ (function () {
this.getType('Query').fields.forEach(function (f) { return _this.queries.set(f.name, f); });
this.getType('Mutation').fields.forEach(function (f) { return _this.mutations.set(f.name, f); });
}
Schema.prototype.determineQueryMode = function () {
var _this = this;
var connection = null;
this.queries.forEach(function (query) {
if (query.type.name && query.type.name.endsWith('TypeConnection')) {
connection = _this.getType(query.type.name);
return false; // break
}
return true;
});
if (!connection) {
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!");
}
if (connection.fields.find(function (f) { return f.name === 'nodes'; })) {
return 'nodes';
}
else if (connection.fields.find(function (f) { return f.name === 'edges'; })) {
return 'edges';
}
else {
return 'plain';
}
};
Schema.prototype.getType = function (name) {
name = upcaseFirstLetter(name);
var type = this.types.get(name);
Expand All @@ -9881,7 +9904,7 @@ var Schema = /** @class */ (function () {
return query;
};
Schema.prototype.returnsConnection = function (field) {
return field.type.name.endsWith('TypeConnection');
return (field.type.name && field.type.name.endsWith('TypeConnection'));
};
return Schema;
}());
Expand Down Expand Up @@ -9947,6 +9970,10 @@ var Context = /** @class */ (function () {
* @type {boolean}
*/
this.debugMode = false;
/**
* Defines how to query connections. 'auto' | 'nodes' | 'edges' | 'plain'
*/
this.connectionQueryMode = 'auto';
this.components = components;
this.options = options;
this.database = options.database;
Expand Down Expand Up @@ -9989,6 +10016,12 @@ var Context = /** @class */ (function () {
case 0:
if (!!this.schema) return [3 /*break*/, 2];
this.logger.log('Fetching GraphQL Schema initially ...');
if (this.options.connectionQueryMode) {
this.connectionQueryMode = this.options.connectionQueryMode;
}
else {
this.connectionQueryMode = 'auto';
}
context = {
headers: { 'X-GraphQL-Introspection-Query': 'true' }
};
Expand All @@ -9999,7 +10032,7 @@ var Context = /** @class */ (function () {
this.logger.log('GraphQL Schema successful fetched', result);
this.logger.log('Starting to process the schema ...');
this.processSchema();
this.logger.log('Schema procession done ...');
this.logger.log('Schema procession done!');
_a.label = 2;
case 2: return [2 /*return*/, this.schema];
}
Expand Down Expand Up @@ -10028,6 +10061,14 @@ var Context = /** @class */ (function () {
}
});
});
if (this.connectionQueryMode === 'auto') {
this.connectionQueryMode = this.schema.determineQueryMode();
this.logger.log("Connection Query Mode is " + this.connectionQueryMode + " by automatic detection");
}
else {
console.log('---> Config!');
this.logger.log("Connection Query Mode is " + this.connectionQueryMode + " by config");
}
};
/**
* Returns a model from the model collection by it's name
Expand Down Expand Up @@ -10092,7 +10133,16 @@ var QueryBuilder = /** @class */ (function () {
var params = this.buildArguments(model, args, false, filter, allowIdFields);
var fields = "\n " + model.getQueryFields().join(' ') + "\n " + this.buildRelationsQuery(model, ignoreRelations) + "\n ";
if (multiple) {
return "\n " + (name ? name : model.pluralName) + params + " {\n nodes {\n " + fields + "\n }\n }\n ";
var header = "" + (name ? name : model.pluralName) + params;
if (context.connectionQueryMode === 'nodes') {
return "\n " + header + " {\n nodes {\n " + fields + "\n }\n }\n ";
}
else if (context.connectionQueryMode === 'edges') {
return "\n " + header + " {\n edges {\n node {\n " + fields + "\n }\n }\n }\n ";
}
else {
return "\n " + header + " {\n " + fields + "\n }\n ";
}
}
else {
return "\n " + (name ? name : model.singularName) + params + " {\n " + fields + "\n }\n ";
Expand Down Expand Up @@ -10219,7 +10269,7 @@ var QueryBuilder = /** @class */ (function () {
var context = Context.getInstance();
var field = model.fields.get(key);
var schemaField = context.schema.getType(model.singularName).fields.find(function (f) { return f.name === key; });
if (schemaField) {
if (schemaField && schemaField.type.name) {
return schemaField.type.name;
}
else {
Expand Down
1 change: 1 addition & 0 deletions docs/.vuepress/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ themeConfig:
- /guide/persist/
- /guide/push/
- /guide/destroy/
- /guide/connection-mode/
- /guide/custom-queries/
- /guide/relationships/
- /guide/eager-loading/
Expand Down
8 changes: 8 additions & 0 deletions docs/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ Vuex-ORM-GraphQL works with the [Apollo Dev Tools](https://chrome.google.com/web
:::


## Connection Mode

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


## License

Vuex-ORM-GraphQL is open-sourced software licensed under the
Expand Down
74 changes: 74 additions & 0 deletions docs/guide/connection-mode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Connection Mode

[[toc]]

It seems that there are several standards within the GraphQL community how connections (fields that returns multiple
records) are designed. Some do this via a `nodes` field, some via a `edges { nodes }` query and some do neither of them.
Vuex-ORM-GraphQL tries to be flexible and supports all of them, but the example queries in the documentation work with
the `nodes` query, don't be irritated.


## Automatic detection

The plugin will try to detect automatically which mode should be used by analyzing the GraphQL Schema. In the best
case you don't have to bother with this at all.


## Manual setting

In rare cases the automatic detection might fail or report the wrong mode. In this case, you can manually set the
`connectionQueryMode` config param to either `auto` (default), `nodes`, `edges`, `plain`. The modes and the resulting
queries are explained in the next sections.


## Mode 1: `nodes`

This is the preferred mode and used for the example queries in this documentation. Setting the connection mode to
`nodes` (or letting the plugin auto detect this mode) will lead to the following query when calling `User.fetch()`:

```
query Users {
users {
nodes {
id
email
name
}
}
}
```


## Mode 2: `edges`

This mode uses a `edges` not to query the edge an then query the `node` within that edge:

```
query Users {
users {
edges {
node {
id
email
name
}
}
}
}
```


## Mode 3: `plain`

The third mode is the less preferred one due to the lack of meta information. In this case we just plain pass the field
queries:

```
query Users {
users {
id
email
name
}
}
```
1 change: 1 addition & 0 deletions docs/guide/setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ These are the possible options to pass when calling `VuexORM.use()`:
- `headers` (optional, default: `{}`) HTTP Headers. See [apollo-link-http])(https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-http#options)
- `credentials` (optional, default: `same-origin`) Credentials Policy. See [apollo-link-http])(https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-http#options)
- `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)
- `connectionQueryMode` (optional, default: `auto`). One of `auto | nodes | edges | plain`. See [Connection Mode](/guide/connection-mode)

More options will come in future releases.

Expand Down
22 changes: 21 additions & 1 deletion src/common/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ export default class Context {
*/
public schema: Schema | undefined;

/**
* Defines how to query connections. 'auto' | 'nodes' | 'edges' | 'plain'
*/
public connectionQueryMode: string = 'auto';

/**
* Private constructor, called by the setup method
*
Expand Down Expand Up @@ -153,6 +158,12 @@ export default class Context {
if (!this.schema) {
this.logger.log('Fetching GraphQL Schema initially ...');

if (this.options.connectionQueryMode) {
this.connectionQueryMode = this.options.connectionQueryMode;
} else {
this.connectionQueryMode = 'auto';
}

// We send a custom header along with the request. This is required for our test suite to mock the schema request.
const context = {
headers: { 'X-GraphQL-Introspection-Query': 'true' }
Expand All @@ -162,9 +173,10 @@ export default class Context {
this.schema = new Schema(result.data.__schema);

this.logger.log('GraphQL Schema successful fetched', result);

this.logger.log('Starting to process the schema ...');
this.processSchema();
this.logger.log('Schema procession done ...');
this.logger.log('Schema procession done!');
}

return this.schema;
Expand Down Expand Up @@ -193,6 +205,14 @@ export default class Context {
}
});
});

if (this.connectionQueryMode === 'auto') {
this.connectionQueryMode = this.schema!.determineQueryMode();
this.logger.log(`Connection Query Mode is ${this.connectionQueryMode} by automatic detection`);
} else {
console.log('---> Config!');
this.logger.log(`Connection Query Mode is ${this.connectionQueryMode} by config`);
}
}

/**
Expand Down
32 changes: 26 additions & 6 deletions src/graphql/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,33 @@ export default class QueryBuilder {
`;

if (multiple) {
return `
${name ? name : model.pluralName}${params} {
nodes {
const header: string = `${name ? name : model.pluralName}${params}`;

if (context.connectionQueryMode === 'nodes') {
return `
${header} {
nodes {
${fields}
}
}
`;
} else if (context.connectionQueryMode === 'edges') {
return `
${header} {
edges {
node {
${fields}
}
}
}
`;
} else {
return `
${header} {
${fields}
}
}
`;
`;
}
} else {
return `
${name ? name : model.singularName}${params} {
Expand Down Expand Up @@ -190,7 +210,7 @@ export default class QueryBuilder {

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

if (schemaField) {
if (schemaField && schemaField.type.name) {
return schemaField.type.name;
} else {
if (field instanceof context.components.String) {
Expand Down
28 changes: 26 additions & 2 deletions src/graphql/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ export default class Schema {
this.getType('Mutation').fields!.forEach(f => this.mutations.set(f.name, f));
}

public determineQueryMode (): string {
let connection: GraphQLType | null = null;

this.queries.forEach((query) => {
if (query.type.name && query.type.name.endsWith('TypeConnection')) {
connection = this.getType(query.type.name);
return false; // break
}
return true;
});

if (!connection) {
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!");
}

if (connection!.fields!.find(f => f.name === 'nodes')) {
return 'nodes';
} else if (connection!.fields!.find(f => f.name === 'edges')) {
return 'edges';
} else {
return 'plain';
}
}

public getType (name: string): GraphQLType {
name = upcaseFirstLetter(name);
const type = this.types.get(name);
Expand All @@ -44,7 +68,7 @@ export default class Schema {
return query;
}

public returnsConnection (field: GraphQLField) {
return field.type.name.endsWith('TypeConnection');
public returnsConnection (field: GraphQLField): boolean {
return (field.type.name && field.type.name.endsWith('TypeConnection')) as boolean;
}
}
3 changes: 2 additions & 1 deletion src/support/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Options {
credentials?: string;
useGETForQueries?: boolean;
debug?: boolean;
connectionQueryMode?: string;
}

export interface ActionParams {
Expand Down Expand Up @@ -58,7 +59,7 @@ export interface GraphQLField {

export interface GraphQLTypeDefinition {
kind: string;
name: string;
name?: string;
}

export interface GraphQLSchema {
Expand Down
Loading