Skip to content

Commit 6c41c34

Browse files
committed
Determine multiple flag from the schema
1 parent 5de0aa8 commit 6c41c34

File tree

11 files changed

+153
-72
lines changed

11 files changed

+153
-72
lines changed

dist/vuex-orm-graphql.esm.js

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9870,7 +9870,7 @@ var Schema = /** @class */ (function () {
98709870
return mutation;
98719871
};
98729872
Schema.prototype.getQuery = function (name) {
9873-
var query = this.types.get(name);
9873+
var query = this.queries.get(name);
98749874
if (!query)
98759875
throw new Error("Couldn't find Query of name " + name + " in the GraphQL Schema.");
98769876
return query;
@@ -9996,7 +9996,7 @@ var Context = /** @class */ (function () {
99969996
this.processSchema();
99979997
this.logger.log('Schema procession done ...');
99989998
_a.label = 2;
9999-
case 2: return [2 /*return*/];
9999+
case 2: return [2 /*return*/, this.schema];
1000010000
}
1000110001
});
1000210002
});
@@ -10451,18 +10451,18 @@ var Action = /** @class */ (function () {
1045110451
* @param {boolean} multiple Tells if we're requesting a single record or multiple.
1045210452
* @returns {Promise<any>}
1045310453
*/
10454-
Action.mutation = function (name, variables, dispatch, model, multiple) {
10455-
if (multiple === void 0) { multiple = false; }
10454+
Action.mutation = function (name, variables, dispatch, model) {
1045610455
return __awaiter$3(this, void 0, void 0, function () {
10457-
var context, query, newData, insertedData, records, newRecord;
10456+
var context, schema, multiple, query, newData, insertedData, records, newRecord;
1045810457
return __generator$3(this, function (_a) {
1045910458
switch (_a.label) {
1046010459
case 0:
1046110460
if (!variables) return [3 /*break*/, 5];
1046210461
context = Context.getInstance();
1046310462
return [4 /*yield*/, context.loadSchema()];
1046410463
case 1:
10465-
_a.sent();
10464+
schema = _a.sent();
10465+
multiple = schema.returnsConnection(schema.getMutation(name));
1046610466
query = QueryBuilder.buildQuery('mutation', model, name, variables, multiple);
1046710467
return [4 /*yield*/, Context.getInstance().apollo.request(model, query, variables, true)];
1046810468
case 2:
@@ -10776,25 +10776,26 @@ var Mutate = /** @class */ (function (_super) {
1077610776
*/
1077710777
Mutate.call = function (_a, _b) {
1077810778
var state = _a.state, dispatch = _a.dispatch;
10779-
var args = _b.args, multiple = _b.multiple, name = _b.name;
10779+
var args = _b.args, name = _b.name;
1078010780
return __awaiter$6(this, void 0, void 0, function () {
10781-
var model;
10781+
var context, schema, model;
1078210782
return __generator$6(this, function (_c) {
10783-
if (name) {
10784-
model = this.getModelFromState(state);
10785-
args = this.prepareArgs(args);
10786-
// There could be anything in the args, but we have to be sure that all records are gone through
10787-
// transformOutgoingData()
10788-
this.transformArgs(args);
10789-
if (multiple === undefined)
10790-
multiple = !args['id'];
10791-
// Send the mutation
10792-
return [2 /*return*/, Action.mutation(name, args, dispatch, model, multiple)];
10793-
}
10794-
else {
10795-
throw new Error("The mutate action requires the mutation name ('mutation') to be set");
10783+
switch (_c.label) {
10784+
case 0:
10785+
if (!name) return [3 /*break*/, 2];
10786+
context = Context.getInstance();
10787+
return [4 /*yield*/, context.loadSchema()];
10788+
case 1:
10789+
schema = _c.sent();
10790+
model = this.getModelFromState(state);
10791+
args = this.prepareArgs(args);
10792+
// There could be anything in the args, but we have to be sure that all records are gone through
10793+
// transformOutgoingData()
10794+
this.transformArgs(args);
10795+
// Send the mutation
10796+
return [2 /*return*/, Action.mutation(name, args, dispatch, model)];
10797+
case 2: throw new Error("The mutate action requires the mutation name ('mutation') to be set");
1079610798
}
10797-
return [2 /*return*/];
1079810799
});
1079910800
});
1080010801
};
@@ -11060,22 +11061,21 @@ var Query = /** @class */ (function (_super) {
1106011061
*/
1106111062
Query.call = function (_a, _b) {
1106211063
var state = _a.state, dispatch = _a.dispatch;
11063-
var name = _b.name, multiple = _b.multiple, filter = _b.filter, bypassCache = _b.bypassCache;
11064+
var name = _b.name, filter = _b.filter, bypassCache = _b.bypassCache;
1106411065
return __awaiter$9(this, void 0, void 0, function () {
11065-
var context, model, query, data;
11066+
var context, schema, model, multiple, query, data;
1106611067
return __generator$9(this, function (_c) {
1106711068
switch (_c.label) {
1106811069
case 0:
1106911070
if (!name) return [3 /*break*/, 3];
1107011071
context = Context.getInstance();
1107111072
return [4 /*yield*/, context.loadSchema()];
1107211073
case 1:
11073-
_c.sent();
11074+
schema = _c.sent();
1107411075
model = this.getModelFromState(state);
1107511076
// Filter
1107611077
filter = filter ? Transformer.transformOutgoingData(model, filter) : {};
11077-
if (multiple === undefined)
11078-
multiple = !filter['id'];
11078+
multiple = schema.returnsConnection(schema.getQuery(name));
1107911079
query = QueryBuilder.buildQuery('query', model, name, filter, multiple, false);
1108011080
return [4 /*yield*/, context.apollo.request(model, query, filter, false, bypassCache)];
1108111081
case 2:

docs/guide/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ Vuex-ORM-GraphQL is a plugin for the amazing [Vuex-ORM](https://github.com/vuex-
44
Object-Relational Mapping access to the Vuex Store. Vuex-ORM-GraphQL enhances Vuex-ORM to let you sync your Vuex state
55
via the Vuex-ORM models with your server via a [GraphQL API](http://graphql.org/).
66

7-
The plugin will automatically generate GraphQL queries and mutations based on your model definitions and thus hides
8-
the specifics of Network Communication, GraphQL, Caching, De- and Serialization of your Data and so on from the
9-
developer. Getting a record of a model from the server is as easy as calling `Product.fetch()`. This allows you to write
10-
sophisticated Single-Page Applications fast and efficient without worrying about GraphQL.
7+
The plugin will automatically generate GraphQL queries and mutations based on your model definitions and by
8+
reading your and GraphQL schema from your server. Thus it hides the specifics of Network Communication, GraphQL,
9+
Caching, De- and Serialization of your Data and so on from the developer. Getting a record of a model from the server
10+
is as easy as calling `Product.fetch()`. This allows you to write sophisticated Single-Page Applications fast and
11+
efficient without worrying about GraphQL.
1112

1213

1314
::: warning
14-
You should have basic knowledge of [Vue](https://vuejs.org/), [Vuex](https://vuex.vuejs.org/) and
15-
[Vuex-ORM](https://vuex-orm.github.io/vuex-orm/) before reading this documentation.
15+
You should have basic knowledge of [GraphQL](http://graphql.org/), [Vue](https://vuejs.org/),
16+
[Vuex](https://vuex.vuejs.org/) and [Vuex-ORM](https://vuex-orm.github.io/vuex-orm/) before reading this documentation.
1617
:::
1718

1819

docs/guide/custom-queries/README.md

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ await Post.dispatch('query', { name: 'example', filter: { id: post.id } });
3838
As you can see you have to provide the query name and any further arguments you want to pass. In this case we send
3939
the post id, but this could be anything else. Please also notice that `record.$customQuery` automatically adds the id
4040
of the record into the arguments list. The plugin automatically determines if there are multiple records or a single
41-
record is requests by looking in the arguments hash if there is a `id` field and respectively setups the query.
41+
record is returned by looking in the arguments hash if there is a `id` field and respectively setups the query.
4242

4343
A model related custom query is always tied to the model, so the plugin expects the return value of the custom query
44-
is of the model type. In this example that means, that Vuex-ORM-GraphQL expects that the `example` query is of type `Post`.
44+
is of the model type. In this example that means, that Vuex-ORM-GraphQL expects that the `example` query is of type
45+
`Post`.
4546

4647
This generates the following query:
4748

@@ -76,7 +77,6 @@ database.
7677
Following fields are allowed:
7778

7879
- `name`: Required. The name of the query.
79-
- `multiple`: Whether the query is of a connection type (multiple records are returned) or not (single record is returned).
8080
- `filter`: Hash map with filters. These are passed as a filter typed variable like in fetch.
8181
- `bypassCache`: Whether to bypass the caching.
8282

@@ -176,7 +176,6 @@ database.
176176
Following fields are allowed:
177177

178178
- `name`: Required. The name of the mutation.
179-
- `multiple`: Whether the mutation is of a connection type (multiple records are returned) or not (single record is returned).
180179
- `args`: Hash map with arguments (variables).
181180

182181

@@ -220,14 +219,5 @@ Following fields are allowed:
220219

221220
## Multiple or single record
222221

223-
Vuex-ORM-GraphQL tries to determine automatically if a single record or a connection (multiple records) is returned by
224-
a query/mutation via checking if a `id` field is set in the filter/args/variables. But sometimes you have a
225-
query/mutation without ID but it still returns a single record or vice versa. For this case you can manually set the
226-
`multiple` field to tell the plugin how the result is shaped:
227-
228-
```javascript
229-
await Post.customQuery({ name: 'example', multiple: true, filter: { id: post.id } });
230-
```
231-
232-
In future versions of this project this might be obsolete because it could consume and analyze the schema to know
233-
how the queries and mutations are shaped.
222+
Vuex-ORM-GraphQL will determine automatically if a single record or a connection (multiple records) is returned by a
223+
query/mutation via checking your GraphQL Schema. How smart is this?

docs/guide/destroy/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ the following GraphQL query is generated:
2323
mutation DeletePost($id: ID!) {
2424
deletePost(id: $id) {
2525
id
26+
title
27+
content
28+
29+
user {
30+
id
31+
email
32+
}
2633
}
2734
}
2835
```

src/actions/action.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Model from '../orm/model';
66
import State from '@vuex-orm/core/lib/modules/State';
77
import Transformer from '../graphql/transformer';
88
import NameGenerator from '../graphql/name-generator';
9+
import Schema from '../graphql/schema';
910

1011
const inflection = require('inflection');
1112

@@ -24,11 +25,12 @@ export default class Action {
2425
* @returns {Promise<any>}
2526
*/
2627
protected static async mutation (name: string, variables: Data | undefined, dispatch: DispatchFunction,
27-
model: Model, multiple: boolean = false): Promise<any> {
28+
model: Model): Promise<any> {
2829
if (variables) {
29-
const context = Context.getInstance();
30-
await context.loadSchema();
30+
const context: Context = Context.getInstance();
31+
const schema: Schema = await context.loadSchema();
3132

33+
const multiple: boolean = schema.returnsConnection(schema.getMutation(name));
3234
const query = QueryBuilder.buildQuery('mutation', model, name, variables, multiple);
3335

3436
// Send GraphQL Mutation

src/actions/mutate.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { ActionParams, Arguments, Data } from '../support/interfaces';
22
import Action from './action';
3+
import Context from '../common/context';
4+
import Schema from '../graphql/schema';
35

46
/**
57
* Mutate action for sending a custom mutation. Will be used for Model.mutate() and record.$mutate().
@@ -13,19 +15,19 @@ export default class Mutate extends Action {
1315
* @param {Arguments} args Arguments for the mutation. Must contain a 'mutation' field.
1416
* @returns {Promise<Data>} The new record if any
1517
*/
16-
public static async call ({ state, dispatch }: ActionParams, { args, multiple, name }: ActionParams): Promise<Data> {
18+
public static async call ({ state, dispatch }: ActionParams, { args, name }: ActionParams): Promise<Data> {
1719
if (name) {
20+
const context: Context = Context.getInstance();
21+
const schema: Schema = await context.loadSchema();
1822
const model = this.getModelFromState(state);
1923
args = this.prepareArgs(args);
2024

2125
// There could be anything in the args, but we have to be sure that all records are gone through
2226
// transformOutgoingData()
2327
this.transformArgs(args);
2428

25-
if (multiple === undefined) multiple = !args['id'];
26-
2729
// Send the mutation
28-
return Action.mutation(name, args, dispatch, model, multiple);
30+
return Action.mutation(name, args, dispatch, model);
2931
} else {
3032
throw new Error("The mutate action requires the mutation name ('mutation') to be set");
3133
}

src/actions/query.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Transformer from '../graphql/transformer';
55
import { ActionParams, Data } from '../support/interfaces';
66
import Action from './action';
77
import NameGenerator from '../graphql/name-generator';
8+
import Schema from '../graphql/schema';
89

910
/**
1011
* Query action for sending a custom query. Will be used for Model.customQuery() and record.$customQuery.
@@ -20,17 +21,17 @@ export default class Query extends Action {
2021
* @returns {Promise<Data>} The fetched records as hash
2122
*/
2223
public static async call ({ state, dispatch }: ActionParams,
23-
{ name, multiple, filter, bypassCache }: ActionParams): Promise<Data> {
24+
{ name, filter, bypassCache }: ActionParams): Promise<Data> {
2425
if (name) {
25-
const context = Context.getInstance();
26-
await context.loadSchema();
27-
26+
const context: Context = Context.getInstance();
27+
const schema: Schema = await context.loadSchema();
2828
const model = this.getModelFromState(state);
2929

3030
// Filter
3131
filter = filter ? Transformer.transformOutgoingData(model, filter) : {};
3232

33-
if (multiple === undefined) multiple = !filter['id'];
33+
// Multiple?
34+
const multiple: boolean = schema.returnsConnection(schema.getQuery(name));
3435

3536
// Build query
3637
const query = QueryBuilder.buildQuery('query', model, name, filter, multiple, false);

src/common/context.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export default class Context {
9797
/**
9898
* The graphql schema. Is null until the first request.
9999
*/
100-
private schema: Schema | undefined;
100+
public schema: Schema | undefined;
101101

102102
/**
103103
* Private constructor, called by the setup method
@@ -149,7 +149,7 @@ export default class Context {
149149
return this.instance;
150150
}
151151

152-
public async loadSchema () {
152+
public async loadSchema (): Promise<Schema> {
153153
if (!this.schema) {
154154
this.logger.log('Fetching GraphQL Schema initially ...');
155155

@@ -166,6 +166,8 @@ export default class Context {
166166
this.processSchema();
167167
this.logger.log('Schema procession done ...');
168168
}
169+
170+
return this.schema;
169171
}
170172

171173
/**

src/graphql/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default class Schema {
3636
return mutation;
3737
}
3838

39-
public getQuery (name: string): GraphQLType {
39+
public getQuery (name: string): GraphQLField {
4040
const query = this.queries.get(name);
4141

4242
if (!query) throw new Error(`Couldn't find Query of name ${name} in the GraphQL Schema.`);

test/integration/VuexORMGraphQL.spec.js

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -396,25 +396,71 @@ mutation UpdateUser($id: ID!, $user: UserInput!) {
396396
it('sends the correct query to the API', async () => {
397397
const response = {
398398
data: {
399-
deleteUser: {
400-
__typename: 'user',
401-
id: 1,
402-
name: 'Charlie Brown'
399+
deletePost: {
400+
__typename: 'post',
401+
id: 42,
402+
otherId: 13548,
403+
published: true,
404+
title: 'Example Post 5',
405+
content: 'Foo',
406+
comments: {
407+
__typename: 'comment',
408+
nodes: [{
409+
__typename: 'comment',
410+
id: 15,
411+
content: 'Works!',
412+
subjectId: 42,
413+
subjectType: 'Post',
414+
user: {
415+
__typename: 'user',
416+
id: 2,
417+
name: 'Charly Brown'
418+
}
419+
}]
420+
},
421+
user: {
422+
__typename: 'user',
423+
id: 1,
424+
name: 'Johnny Imba',
425+
}
403426
}
404427
}
405428
};
406429

407430
const request = await sendWithMockFetch(response, async () => {
408-
const user = User.find(1);
409-
await user.$destroy();
431+
const post = Post.find(1);
432+
await post.$destroy();
410433
});
411434

412435
expect(request.variables).toEqual({ id: 1 });
413436
expect(request.query).toEqual(`
414-
mutation DeleteUser($id: ID!) {
415-
deleteUser(id: $id) {
437+
mutation DeletePost($id: ID!) {
438+
deletePost(id: $id) {
416439
id
417-
name
440+
content
441+
title
442+
otherId
443+
published
444+
user {
445+
id
446+
name
447+
__typename
448+
}
449+
comments {
450+
nodes {
451+
id
452+
content
453+
subjectId
454+
subjectType
455+
user {
456+
id
457+
name
458+
__typename
459+
}
460+
__typename
461+
}
462+
__typename
463+
}
418464
__typename
419465
}
420466
}

0 commit comments

Comments
 (0)