Compare commits
86 Commits
v0.0.2
...
v0.1.0-alpha.2
| Author | SHA1 | Date | |
|---|---|---|---|
| a318b10e97 | |||
| 1ca08f363c | |||
| 0d68177297 | |||
| 47a9363624 | |||
| 02a95b0068 | |||
| 5f77e78dce | |||
| a55c4df8ac | |||
| ffe674e179 | |||
| 5316dffc79 | |||
| 2c102ffd86 | |||
| 653651af9e | |||
| 2579221ef3 | |||
| 1671a2a641 | |||
| 271af35ff3 | |||
| 38efa7f5fd | |||
| a184514f2c | |||
| 7a79a5c1fa | |||
| 4d55c37949 | |||
| 62e5db0159 | |||
| efe3394cc7 | |||
| 631f1c8836 | |||
| b0060fa347 | |||
| 51af6f30e4 | |||
| 0325a5bd2e | |||
| 46569adf84 | |||
| 1ada9125b7 | |||
| 0a260bd78e | |||
| f9415e58ba | |||
| 1abd6285a2 | |||
| 93d1f2b3fb | |||
| 3baa30e4b8 | |||
| b76d2b89f8 | |||
| 7593629c61 | |||
| 3fc3e664f4 | |||
| 2a24baba26 | |||
| 7008d8a401 | |||
| 75679dc539 | |||
| d6a4af0adf | |||
| 1e4bb13b14 | |||
| 345bb7e0b6 | |||
| fe75aaaf03 | |||
| 4f6b27c3cc | |||
| 60e04d13a2 | |||
| a5af67e962 | |||
| 70831ef914 | |||
| 3e20a074bf | |||
| 711ad93254 | |||
| 95a4720a24 | |||
| 2f26bfd45a | |||
| a00b05b352 | |||
| 5802d949e5 | |||
| 860db84f51 | |||
| 9ec02d15f0 | |||
| cb7ef32616 | |||
| 2e160a6f29 | |||
| 99f815dfbd | |||
| b10f95bd02 | |||
| a56f0168dd | |||
| 3426e2aff4 | |||
| 8571bc04ab | |||
| bb4b2b875e | |||
| f3382b7d76 | |||
| b0fc7d5506 | |||
| 33068e663b | |||
| 6ed1e9bdb1 | |||
| e6a80a4d88 | |||
| 2fe8c6b582 | |||
| 6bc078dbe4 | |||
| 7fd1b3977b | |||
| cce1dbd5a9 | |||
| 1c314402c5 | |||
| 2dae141096 | |||
| b58b07cf9b | |||
| 3b15071a3f | |||
| 770c2e3b98 | |||
| 6f33c7d785 | |||
| ced75f75ae | |||
| 3815b97099 | |||
| 5626dc129b | |||
| 7cacc37645 | |||
| 9f15bc22c5 | |||
| 16ca4b711b | |||
| e4a814126b | |||
| 4f6ddc91ba | |||
| 36ddad8c67 | |||
| 301e22b24e |
@@ -1,3 +1,151 @@
|
||||
# vue-apollo-smart-ops
|
||||
|
||||
Create TypeScript-typed operation functions for your Vue Apollo queries and mutations.
|
||||
Creates TypeScript-typed operation functions for GraphQL queries and mutations compatible with
|
||||
[Vue Apollo](https://apollo.vuejs.org/).
|
||||
|
||||
This library is intended to be used together with the
|
||||
[`typescript-vue-apollo-smart-ops` plugin](https://www.graphql-code-generator.com/docs/plugins/typescript-vue-apollo-smart-ops)
|
||||
for [GraphQL Code Generator](https://www.graphql-code-generator.com/), but it may also be useful standalone.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
npm install --save vue-apollo-smart-ops
|
||||
```
|
||||
|
||||
## Smart Query Usage
|
||||
|
||||
### `createSmartQueryOptionsFunction(query, onError?)`
|
||||
|
||||
Returns a generated function which returns a [Vue Apollo Smart Query options object](https://apollo.vuejs.org/api/smart-query.html#options)
|
||||
for the given query when called.
|
||||
|
||||
> ⚠️ Note: The returned function is not meant to execute the query itself. It is only used to configure a Vue Apollo
|
||||
> Smart Query. The responsibility for executing the query lies with Vue Apollo.
|
||||
|
||||
The returned function accepts an options object as its first argument, allowing variables and other parameters to be
|
||||
customised in runtime usage. Compared with creating an options object directly, the advantage here is that the options
|
||||
accepted by the generated function are fully type-checked based on the query definition - and without needing to pass
|
||||
type arguments at every usage.
|
||||
|
||||
Using the [`@graphql-codegen/typescript-vue-apollo-smart-ops` plugin](https://www.graphql-code-generator.com/docs/plugins/typescript-vue-apollo-smart-ops)
|
||||
you can automatically generate options functions for all of the query operations in your project.
|
||||
|
||||
The following example manually creates an options function and assigns it to the variable `useTodoListQuery`:
|
||||
|
||||
```typescript
|
||||
const useTodoListQuery = createSmartQueryOptionsFunction<TodosQuery, TodosQueryVariables>(gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`);
|
||||
```
|
||||
|
||||
This function can subsequently be called inside the `apollo` options of a Vue component to configure a Smart Query. The
|
||||
following example adds a `todos` Smart Query to a component, in Vue class component style:
|
||||
|
||||
```typescript
|
||||
@Component<TodoList>({
|
||||
apollo: {
|
||||
todos: useTodoListQuery<TodoList>({
|
||||
skip() {
|
||||
return this.foo === 'bar';
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
limit: 10,
|
||||
skip: 0,
|
||||
};
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
class TodoList extends Vue {
|
||||
todos: Todo[] | null = null;
|
||||
|
||||
get foo(): string {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `@SmartQuery(options)`
|
||||
|
||||
The `@SmartQuery()` decorator works with your generated options functions to enable the declaration of Smart Queries
|
||||
within the body of a Vue class component, instead of in the component options. This helps to keep the data property and
|
||||
its query options together in one place.
|
||||
|
||||
The following example is equivalent to the previous example but using the decorated syntax style:
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
class TodoList extends Vue {
|
||||
@SmartQuery(
|
||||
useTodoListQuery<TodoList>({
|
||||
skip() {
|
||||
return this.foo === 'bar';
|
||||
},
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
}),
|
||||
)
|
||||
todos: Todo[] | null = null;
|
||||
|
||||
get foo(): string {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mutations Usage
|
||||
|
||||
### `createMutationFunction(mutation, onError?)`
|
||||
|
||||
Returns a generated function which executes a mutation and returns the result when called.
|
||||
|
||||
The returned function requires a Vue app instance as its first argument (from which the `$apollo` client will be
|
||||
accessed), and accepts an options object as its second argument, allowing variables and other parameters to be
|
||||
customised in runtime usage. Compared with executing a mutation using the Vue Apollo client directly, the advantage here
|
||||
is that the options accepted by the generated function are fully type-checked based on the operation definition - and
|
||||
without needing to pass type arguments at every usage.
|
||||
|
||||
Using the [`@graphql-codegen/typescript-vue-apollo-smart-ops` plugin](https://www.graphql-code-generator.com/docs/plugins/typescript-vue-apollo-smart-ops)
|
||||
you can automatically generate mutation functions for all of the mutation operations in your project.
|
||||
|
||||
The following example manually creates a mutation function and assigns it to the variable `todoCreateMutation`:
|
||||
|
||||
```typescript
|
||||
const todoCreateMutation = createMutationFunction<TodoCreateMutation, TodoCreateMutationVariables>(gql`
|
||||
mutation TodoCreate($input: TodoInput!) {
|
||||
todoCreate(input: $input) {
|
||||
todo {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
```
|
||||
|
||||
The following example demonstrates how to call this mutation from a method on a component:
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
class TodoList extends Vue {
|
||||
async onClickCreateTodoButton(): Promise<void> {
|
||||
const result = await todoCreateMutation(this, {
|
||||
variables: {
|
||||
title: 'Bake a cake',
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`Failed to create Todo!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
+15
-22
@@ -1,24 +1,17 @@
|
||||
const { pathsToModuleNameMapper } = require('ts-jest/utils');
|
||||
const { compilerOptions } = require('./tsconfig');
|
||||
|
||||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/$1',
|
||||
'^~/(.*)$': '<rootDir>/$1',
|
||||
'^vue$': 'vue/dist/vue.common.js',
|
||||
},
|
||||
moduleFileExtensions: ['js', 'ts', 'vue', 'json'],
|
||||
transform: {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
'^.+\\.ts?$': 'ts-jest',
|
||||
'.*\\.(vue)$': 'vue-jest',
|
||||
},
|
||||
collectCoverage: false,
|
||||
collectCoverageFrom: ['<rootDir>/components/**/*.vue', '<rootDir>/pages/**/*.vue'],
|
||||
reporters: [
|
||||
'default',
|
||||
[
|
||||
'jest-html-reporters',
|
||||
{
|
||||
filename: 'test/.results/test-results.html',
|
||||
},
|
||||
],
|
||||
],
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src/', '<rootDir>/test/'],
|
||||
testRegex: '\\.(spec|e2e-spec)\\.ts$',
|
||||
testPathIgnorePatterns: ['/node_modules/'],
|
||||
testTimeout: 20000,
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
|
||||
prefix: '<rootDir>/',
|
||||
}),
|
||||
coveragePathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/test/'],
|
||||
coverageDirectory: '<rootDir>/test/.coverage',
|
||||
reporters: ['default'],
|
||||
};
|
||||
|
||||
Generated
+2781
-3140
File diff suppressed because it is too large
Load Diff
+25
-21
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-apollo-smart-ops",
|
||||
"version": "0.0.2",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"description": "Create TypeScript-typed operation functions for your Vue Apollo queries and mutations.",
|
||||
"author": "Madscience Ltd",
|
||||
"license": "MIT",
|
||||
@@ -19,7 +19,7 @@
|
||||
"lint:fix": "eslint --ext .ts --ignore-path .gitignore . --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:ci": "rimraf test/.results && mkdirp test/.results && jest --ci --runInBand --passWithNoTests",
|
||||
"test:ci": "jest --ci --runInBand",
|
||||
"version": "npm run build",
|
||||
"postversion": "npm run postbuild",
|
||||
"release": "np --contents dist/",
|
||||
@@ -29,6 +29,9 @@
|
||||
"lodash.isplainobject": "^4.0",
|
||||
"lodash.mapvalues": "^4.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"vue-class-component": "^7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"apollo-client": ">=2.6",
|
||||
"apollo-link": ">=1.2",
|
||||
@@ -37,38 +40,39 @@
|
||||
"vue-apollo": ">=3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "26.0.22",
|
||||
"@types/jest": "27.0.1",
|
||||
"@types/lodash.isplainobject": "4.0.6",
|
||||
"@types/lodash.mapvalues": "4.6.6",
|
||||
"@typescript-eslint/eslint-plugin": "4.21.0",
|
||||
"@typescript-eslint/parser": "4.21.0",
|
||||
"@vue/test-utils": "1.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "4.30.0",
|
||||
"@typescript-eslint/parser": "4.30.0",
|
||||
"@vue/test-utils": "1.2.2",
|
||||
"apollo-client": "2.6.10",
|
||||
"apollo-link": "1.2.14",
|
||||
"barrelsby": "2.2.0",
|
||||
"dotenv-cli": "4.0.0",
|
||||
"eslint": "7.23.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jest": "24.3.4",
|
||||
"eslint-plugin-import": "2.24.2",
|
||||
"eslint-plugin-jest": "24.4.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"eslint-plugin-prettier": "3.4.1",
|
||||
"eslint-plugin-promise": "4.3.1",
|
||||
"eslint-plugin-vue": "7.8.0",
|
||||
"graphql": "15.5.0",
|
||||
"husky": "6.0.0",
|
||||
"jest": "26.6.3",
|
||||
"jest-html-reporters": "2.1.3",
|
||||
"lint-staged": "10.5.4",
|
||||
"eslint-plugin-vue": "7.17.0",
|
||||
"graphql": "15.5.2",
|
||||
"graphql-tag": "2.12.5",
|
||||
"husky": "7.0.2",
|
||||
"jest": "27.1.0",
|
||||
"lint-staged": "11.1.2",
|
||||
"mkdirp": "1.0.4",
|
||||
"np": "7.4.0",
|
||||
"np": "7.5.0",
|
||||
"prettier": "2.2.1",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "26.5.4",
|
||||
"typescript": "4.2.3",
|
||||
"vue": "2.6.12",
|
||||
"ts-jest": "27.0.5",
|
||||
"typescript": "4.2.4",
|
||||
"vue": "2.6.14",
|
||||
"vue-apollo": "3.0.7",
|
||||
"vue-jest": "3.0.7"
|
||||
"vue-class-component": "7.2.6",
|
||||
"vue-property-decorator": "9.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.9.0"
|
||||
|
||||
@@ -18,7 +18,7 @@ export function isGraphQLError(error: GraphQLError | any): error is GraphQLError
|
||||
return error.extensions !== undefined;
|
||||
}
|
||||
|
||||
export abstract class AbstractApolloErrorProcessor<TApp = Vue, TContext = ApolloOperationContext> {
|
||||
export class ApolloErrorProcessor<TApp = Vue, TContext = ApolloOperationContext> {
|
||||
public static FriendlyMessages: Record<string, string> = {
|
||||
FAILED_TO_FETCH:
|
||||
'Unable to communicate with server. The service may be down or you may be offline. Try again in a moment.',
|
||||
@@ -39,7 +39,13 @@ export abstract class AbstractApolloErrorProcessor<TApp = Vue, TContext = Apollo
|
||||
this.processedErrors = this.processApolloError(error);
|
||||
}
|
||||
|
||||
public abstract showErrorNotifications(): void;
|
||||
public showErrorNotifications(): void {
|
||||
// This is just an example - to do something else (e.g. showing a visible notification to the user), you should
|
||||
// implement your own class that extends ApolloErrorProcessor and replace this showErrorNotifications method.
|
||||
this.processedErrors.forEach(error => {
|
||||
console.error(`${error.type}: ${error.message}`, error.error);
|
||||
});
|
||||
}
|
||||
|
||||
public cleanError(error: ApolloError | GraphQLError | Record<string, any>): Error {
|
||||
if (error instanceof Error) {
|
||||
@@ -93,7 +99,7 @@ export abstract class AbstractApolloErrorProcessor<TApp = Vue, TContext = Apollo
|
||||
protected getFriendlyMessage(errorCode: string, errorMessage: string): string;
|
||||
protected getFriendlyMessage(errorCode: string): string | undefined;
|
||||
protected getFriendlyMessage(errorCode: string, errorMessage?: string): string | undefined {
|
||||
return (this.constructor as typeof AbstractApolloErrorProcessor).FriendlyMessages[errorCode] ?? errorMessage;
|
||||
return (this.constructor as typeof ApolloErrorProcessor).FriendlyMessages[errorCode] ?? errorMessage;
|
||||
}
|
||||
|
||||
private processApolloError(error: ApolloError): ProcessedApolloError[] {
|
||||
@@ -0,0 +1,285 @@
|
||||
import { ApolloQueryResult } from 'apollo-client';
|
||||
import gql from 'graphql-tag';
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { createSmartQueryOptionsFunction } from '../query';
|
||||
import { SmartQuery } from './SmartQuery';
|
||||
|
||||
interface Todo {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface QueryResult {
|
||||
todos: Todo[];
|
||||
__typename?: 'Query';
|
||||
}
|
||||
|
||||
interface QueryVariables {
|
||||
skip?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
describe('@SmartQuery() decorator', () => {
|
||||
it('Adds basic query options', () => {
|
||||
@Component
|
||||
class TodoList extends Vue {
|
||||
@SmartQuery({
|
||||
query: gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
})
|
||||
todos!: Todo[];
|
||||
|
||||
get vars(): QueryVariables {
|
||||
return {
|
||||
limit: 10,
|
||||
skip: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TodoList();
|
||||
|
||||
expect(instance.$options.apollo?.todos).toEqual(
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ definitions: expect.arrayContaining([]) }),
|
||||
variables: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Works with a query options function', () => {
|
||||
const useTodoListQuery = createSmartQueryOptionsFunction<QueryResult, QueryVariables>(gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@Component<TodoList>({})
|
||||
class TodoList extends Vue {
|
||||
@SmartQuery(
|
||||
useTodoListQuery<TodoList>({
|
||||
skip() {
|
||||
return this.foo === 'bar';
|
||||
},
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
loadingKey: 'loading',
|
||||
}),
|
||||
)
|
||||
todos!: Todo[];
|
||||
|
||||
loading: number = 0;
|
||||
|
||||
get vars(): QueryVariables {
|
||||
return {
|
||||
limit: 10,
|
||||
skip: 0,
|
||||
};
|
||||
}
|
||||
|
||||
get foo(): string {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TodoList();
|
||||
|
||||
expect(instance.$options.apollo?.todos).toEqual(
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ definitions: expect.arrayContaining([]) }),
|
||||
skip: expect.any(Function),
|
||||
variables: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Works with class inheritance and update, result methods', () => {
|
||||
@Component
|
||||
class TodoList extends Vue {
|
||||
@SmartQuery({
|
||||
query: gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
})
|
||||
todos!: Todo[];
|
||||
|
||||
get vars(): QueryVariables {
|
||||
return {
|
||||
limit: 10,
|
||||
skip: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class TodoList2 extends TodoList {
|
||||
@SmartQuery<TodoList2, QueryResult>({
|
||||
query: gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
update(data: QueryResult) {
|
||||
// data: QueryResult
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
result({ data, errors, loading }) {
|
||||
this.doThings();
|
||||
},
|
||||
subscribeToMore: {
|
||||
document: gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
},
|
||||
})
|
||||
todos!: Todo[];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
doThings() {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TodoList2();
|
||||
|
||||
expect(instance.$options.apollo?.todos).toEqual(
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ definitions: expect.arrayContaining([]) }),
|
||||
variables: expect.any(Function),
|
||||
update: expect.any(Function),
|
||||
result: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Supports subscribeToMore with updateQuery method', () => {
|
||||
@Component
|
||||
class TodoList extends Vue {
|
||||
@SmartQuery({
|
||||
query: gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
})
|
||||
todos!: Todo[];
|
||||
|
||||
get vars(): QueryVariables {
|
||||
return {
|
||||
limit: 10,
|
||||
skip: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class TodoList3 extends TodoList {
|
||||
@SmartQuery<TodoList3, QueryResult, QueryVariables>({
|
||||
query: gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
update(data: QueryResult) {
|
||||
// data: QueryResult
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
|
||||
result(data: ApolloQueryResult<QueryResult>) {},
|
||||
subscribeToMore: [
|
||||
{
|
||||
document: gql`
|
||||
query Todos($skip: Int, $limit: Int) {
|
||||
todos(skip: $skip, limit: $limit) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return this.vars;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
updateQuery(prev, { subscriptionData: { data }, variables }) {
|
||||
return {
|
||||
...prev,
|
||||
todos: [
|
||||
{ id: '1', title: 'Int' },
|
||||
{ id: '2', title: 'Float' },
|
||||
{ id: '3', title: 'String' },
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
todos!: Todo[];
|
||||
}
|
||||
|
||||
const instance = new TodoList3();
|
||||
|
||||
expect(instance.$options.apollo?.todos).toEqual(
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ definitions: expect.arrayContaining([]) }),
|
||||
variables: expect.any(Function),
|
||||
update: expect.any(Function),
|
||||
result: expect.any(Function),
|
||||
subscribeToMore: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
document: expect.objectContaining({ definitions: expect.arrayContaining([]) }),
|
||||
variables: expect.any(Function),
|
||||
updateQuery: expect.any(Function),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Based on https://github.com/chanlito/vue-apollo-decorator by chanlito ❤️
|
||||
*/
|
||||
|
||||
import { ApolloError, OperationVariables } from 'apollo-client';
|
||||
import { DocumentNode } from 'graphql';
|
||||
import Vue from 'vue';
|
||||
import { createDecorator, VueDecorator } from 'vue-class-component';
|
||||
import { VueApolloSmartQueryOptions } from '../query';
|
||||
|
||||
export function SmartQuery<TApp = any, TResult = any, TVariables = OperationVariables, TError = ApolloError>(
|
||||
options: TApp extends Vue ? VueApolloSmartQueryOptions<TResult, TVariables, TError, TApp> : DocumentNode,
|
||||
): VueDecorator {
|
||||
return createDecorator((componentOptions: any, k: string) => {
|
||||
componentOptions.apollo = componentOptions.apollo || {};
|
||||
componentOptions.apollo[k] = options;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from './SmartQuery';
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ApolloErrorProcessor } from './ApolloErrorProcessor';
|
||||
import {
|
||||
ApolloError,
|
||||
ApolloErrorHandlerResult,
|
||||
ApolloOperationContext,
|
||||
ApolloOperationErrorHandlerFunction,
|
||||
} from './types';
|
||||
import { Vue } from 'vue/types/vue';
|
||||
|
||||
/**
|
||||
* This is a simple example of an error handler function. You can copy this and implement your own in your application.
|
||||
*/
|
||||
export const handleApolloError: ApolloOperationErrorHandlerFunction<ApolloError, Vue> = (
|
||||
error: ApolloError,
|
||||
app: Vue,
|
||||
context?: ApolloOperationContext,
|
||||
): ApolloErrorHandlerResult => {
|
||||
const processor = new ApolloErrorProcessor(error, app, context ?? {});
|
||||
|
||||
processor.showErrorNotifications();
|
||||
|
||||
return {
|
||||
processedErrors: processor.processedErrors,
|
||||
};
|
||||
};
|
||||
+3
-1
@@ -2,8 +2,10 @@
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from './AbstractApolloErrorProcessor';
|
||||
export * from './ApolloErrorProcessor';
|
||||
export * from './handleApolloError';
|
||||
export * from './mutation';
|
||||
export * from './query';
|
||||
export * from './subscription';
|
||||
export * from './types';
|
||||
export * from './decorator/index';
|
||||
|
||||
+5
-1
@@ -90,7 +90,11 @@ export async function mutateWithErrorHandling<
|
||||
client?: ApolloMutationClient<TResult, TVariables>,
|
||||
): Promise<MutationResult<TResult>> {
|
||||
const mutate =
|
||||
client === undefined ? app.$apollo : typeof client === 'function' ? client : client.mutate.bind(client);
|
||||
client === undefined
|
||||
? app.$apollo.mutate.bind(app.$apollo)
|
||||
: typeof client === 'function'
|
||||
? client
|
||||
: client.mutate.bind(client);
|
||||
|
||||
try {
|
||||
const result = await mutate({
|
||||
|
||||
+51
-12
@@ -1,39 +1,78 @@
|
||||
import { VueApolloQueryDefinition, ErrorHandler } from 'vue-apollo/types/options';
|
||||
import { ApolloError, OperationVariables } from 'apollo-client';
|
||||
import { DocumentNode } from 'graphql';
|
||||
import { ErrorHandler, VueApolloQueryDefinition, VueApolloSubscribeToMoreOptions } from 'vue-apollo/types/options';
|
||||
import { Vue } from 'vue/types/vue';
|
||||
import { ApolloOperationErrorHandlerFunction } from './types';
|
||||
|
||||
type OverrideThis<F, T> = F extends (...args: infer A) => infer B ? (this: T, ...args: A) => B : F;
|
||||
|
||||
type OverrideAllThis<O, T> = {
|
||||
[key in keyof O]: OverrideThis<O[key], T>;
|
||||
};
|
||||
|
||||
type SubscribeToMoreOptionsPatched<TComponent, TResult, TVariables> = OverrideAllThis<
|
||||
Omit<VueApolloSubscribeToMoreOptions<TResult, TVariables>, 'updateQuery' | 'variables'>,
|
||||
TComponent
|
||||
> & {
|
||||
variables?: (this: TComponent) => any;
|
||||
updateQuery?: UpdateQueryFn<TComponent, TResult, any, any>; // TODO: How should we pass subscript data & variables types?
|
||||
};
|
||||
|
||||
type UpdateQueryFn<TComponent = any, TResult = any, TSubscriptionVariables = any, TSubscriptionData = any> = (
|
||||
this: TComponent,
|
||||
previousQueryResult: TResult,
|
||||
options: {
|
||||
subscriptionData: {
|
||||
data: TSubscriptionData;
|
||||
};
|
||||
variables?: TSubscriptionVariables;
|
||||
},
|
||||
) => TResult;
|
||||
|
||||
export interface VueApolloQueryDefinitionPatched<TComponent extends Vue = Vue, TResult = any, TVariables = any>
|
||||
extends OverrideAllThis<
|
||||
Omit<VueApolloQueryDefinition<TResult, TVariables>, 'subscribeToMore' | 'variables' | 'loadingKey'>,
|
||||
TComponent
|
||||
> {
|
||||
variables?: ((this: TComponent) => TVariables) | TVariables;
|
||||
subscribeToMore?:
|
||||
| SubscribeToMoreOptionsPatched<TComponent, TResult, TVariables>
|
||||
| Array<SubscribeToMoreOptionsPatched<TComponent, TResult, TVariables>>;
|
||||
loadingKey?: keyof TComponent;
|
||||
}
|
||||
|
||||
export type VueApolloSmartQueryErrorHandler<
|
||||
TResult = any,
|
||||
TVariables = OperationVariables,
|
||||
TError = ApolloError,
|
||||
TApp extends Vue = Vue
|
||||
TComponent extends Vue = Vue
|
||||
> = (
|
||||
error: TError,
|
||||
vm: TApp,
|
||||
vm: TComponent,
|
||||
key: string,
|
||||
type: 'query',
|
||||
options: VueApolloSmartQueryOptions<TResult, TVariables, TError, TApp>,
|
||||
options: VueApolloSmartQueryOptions<TResult, TVariables, TError, TComponent>,
|
||||
) => void;
|
||||
|
||||
// Type of VueApolloQueryDefinition['subscribeToMore'] is incompatible with generated QueryVariables types.
|
||||
// Omitting it here since we don't use it anyway.
|
||||
export type VueApolloSmartQueryOptions<
|
||||
TResult = any,
|
||||
TVariables = OperationVariables,
|
||||
TError = ApolloError,
|
||||
TApp extends Vue = Vue
|
||||
> = Omit<VueApolloQueryDefinition<TResult, TVariables>, 'subscribeToMore' | 'error'> & {
|
||||
error?: VueApolloSmartQueryErrorHandler<TResult, TVariables, TError, TApp>;
|
||||
TComponent extends Vue = Vue
|
||||
> = VueApolloQueryDefinitionPatched<TComponent, TResult, TVariables> & {
|
||||
error?: VueApolloSmartQueryErrorHandler<TResult, TVariables, TError, TComponent>;
|
||||
};
|
||||
|
||||
export type VueApolloSmartQueryOptionsFunction<TResult, TVariables, TError = ApolloError, TApp extends Vue = Vue> = <
|
||||
TComponent extends Vue = TApp
|
||||
>(
|
||||
options?: Partial<Omit<VueApolloSmartQueryOptions<TResult, TVariables, TError, TComponent>, 'query'>>,
|
||||
) => VueApolloSmartQueryOptions<TResult, TVariables, TError, TComponent>;
|
||||
|
||||
export function createSmartQueryOptionsFunction<TResult, TVariables, TError = ApolloError, TApp extends Vue = Vue>(
|
||||
query: DocumentNode,
|
||||
onError?: ApolloOperationErrorHandlerFunction<TError, TApp>,
|
||||
): (
|
||||
options?: Partial<VueApolloSmartQueryOptions<TResult, TVariables, TError, TApp>>,
|
||||
) => VueApolloQueryDefinition<TResult, TVariables> {
|
||||
): VueApolloSmartQueryOptionsFunction<TResult, TVariables> {
|
||||
return (options = {}) => {
|
||||
const defaultErrorHandlerFn: VueApolloSmartQueryErrorHandler<TResult, TVariables, TError, TApp> = (
|
||||
error: TError,
|
||||
|
||||
+4
-3
@@ -17,9 +17,10 @@
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
},
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./@types"
|
||||
"types": [
|
||||
"@types/node",
|
||||
"@types/jest",
|
||||
"vue-apollo/types"
|
||||
]
|
||||
},
|
||||
"include":[
|
||||
|
||||
Reference in New Issue
Block a user