55 Commits

Author SHA1 Message Date
Simon Garner 8ad83a4fc6 0.1.0
Build / dependencies (push) Has been cancelled
Build / lint (push) Has been cancelled
Build / build (push) Has been cancelled
Build / test (push) Has been cancelled
2021-09-08 09:17:14 +12:00
Simon Garner 5e99a3a354 0.1.0-alpha.5
Build / dependencies (push) Has been cancelled
Build / lint (push) Has been cancelled
Build / build (push) Has been cancelled
Build / test (push) Has been cancelled
2021-09-08 08:02:15 +12:00
Simon Garner cbe2c251fb Merge pull request #68 from madscience/fix/mutation-params
Enable mutation functions to accept all MutationOptions
2021-09-08 08:01:20 +12:00
Simon Garner ce94951550 Simplify overriding onError with params 2021-09-08 07:59:34 +12:00
Simon Garner f1969bc88c Enable mutation functions to accept all MutationOptions
The apollo client mutate() function accepts additional parameters (`optimisticResponse`, `update`, etc) that were not supported by the generated mutation operation functions before. Now those parameters will all be passed through.
2021-09-08 07:55:43 +12:00
Simon Garner 7be078995e 0.1.0-alpha.4
Build / dependencies (push) Has been cancelled
Build / lint (push) Has been cancelled
Build / build (push) Has been cancelled
Build / test (push) Has been cancelled
2021-09-07 07:56:22 +12:00
Simon Garner f8b9bbd403 Add missing types for throttle and debounce in query options
See https://github.com/vuejs/vue-apollo/issues/335
2021-09-07 07:55:55 +12:00
Simon Garner 656eef7bcd 0.1.0-alpha.3
Build / dependencies (push) Has been cancelled
Build / lint (push) Has been cancelled
Build / build (push) Has been cancelled
Build / test (push) Has been cancelled
2021-09-06 12:10:00 +12:00
Madbot 6326ec8211 Fix code style issues with Prettier 2021-09-06 12:03:18 +12:00
Simon Garner 512eb9328b fix type conflicts with GraphQLError.message 2021-09-06 12:03:18 +12:00
Renovate Bot 8f4c8cd88f Update dependency typescript to v4.4.2 2021-09-06 12:03:18 +12:00
Madbot 0f312478bc Fix code style issues with Prettier 2021-09-06 11:15:48 +12:00
Renovate Bot b022822a97 Update dependency prettier to v2.3.2 2021-09-06 11:15:48 +12:00
Simon Garner ee396004a5 Rename test workflow -> build 2021-09-06 10:55:30 +12:00
Simon Garner 2077acddcb Use lint action with automatic fixes in CI 2021-09-06 10:55:10 +12:00
Simon Garner e34cfec074 Reformat with prettier 2021-09-06 10:52:12 +12:00
Simon Garner 595733435b Add format script 2021-09-06 10:52:12 +12:00
Simon Garner e610d9dff0 Add prettier ignore file 2021-09-06 10:52:12 +12:00
Simon Garner f5b28b0dc0 Remove eslint-plugin-prettier 2021-09-06 10:52:12 +12:00
Renovate Bot c8f8c6c7b5 Update eslint 2021-09-06 10:52:12 +12:00
Simon Garner 6ce30d8063 Merge pull request #67 from madscience/decorator
Add SmartQuery decorator
2021-09-06 10:25:47 +12:00
Simon Garner c489613d12 Add credits to README 2021-09-06 10:15:51 +12:00
Simon Garner e835aac336 Improve grammar 2021-09-06 10:13:42 +12:00
Simon Garner a318b10e97 0.1.0-alpha.2
Test / dependencies (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled
2021-09-06 09:46:05 +12:00
Simon Garner 1ca08f363c Type loadingKey query option as being a key of the component 2021-09-06 09:45:42 +12:00
Simon Garner 0d68177297 0.1.0-alpha.1 2021-09-05 16:25:52 +12:00
Simon Garner 47a9363624 Remove --passWithNoTests
We have a test now!
2021-09-05 16:25:52 +12:00
Simon Garner 02a95b0068 Simplify headings in README 2021-09-05 16:25:52 +12:00
Simon Garner 5f77e78dce Add usage instructions to README.md 2021-09-05 16:25:52 +12:00
Simon Garner a55c4df8ac Keep empty test directory 2021-09-05 16:25:51 +12:00
Simon Garner ffe674e179 fix lint 2021-09-05 16:25:51 +12:00
Simon Garner 5316dffc79 Don't need to worry about test/.results directory any more 2021-09-05 16:25:51 +12:00
Simon Garner 2c102ffd86 Revert "Run tsc build as part of test script"
This reverts commit b5ebe6cdf5.
2021-09-05 16:25:51 +12:00
Simon Garner 653651af9e Remove example test file 2021-09-05 16:25:51 +12:00
Simon Garner 2579221ef3 Add SmartQuery decorator tests 2021-09-05 16:25:51 +12:00
Simon Garner 1671a2a641 Add VueApolloSmartQueryOptionsFunction type 2021-09-05 16:25:51 +12:00
Simon Garner 271af35ff3 Remove vue-jest, use ts-jest only 2021-09-05 16:25:51 +12:00
Simon Garner 38efa7f5fd Run tsc build as part of test script 2021-09-05 16:25:50 +12:00
Simon Garner a184514f2c Remove jest-html-reporters 2021-09-05 16:25:50 +12:00
Simon Garner 7a79a5c1fa Add test for types and decorator 2021-09-05 16:25:50 +12:00
Simon Garner 4d55c37949 Add SmartQuery decorator 2021-09-05 16:25:50 +12:00
Simon Garner 62e5db0159 Add vue-class-component to optionalDependencies 2021-09-05 16:25:50 +12:00
Renovate Bot efe3394cc7 Update Node.js to v14.17.6 2021-09-03 17:34:00 +00:00
Renovate Bot 631f1c8836 Update eslint to v4.30.0 2021-09-02 18:53:02 +00:00
Renovate Bot b0060fa347 Update dependency graphql to v15.5.2 2021-09-02 18:52:21 +00:00
Renovate Bot 51af6f30e4 Update dependency jest to v27.1.0 2021-08-30 13:07:11 +00:00
Renovate Bot 0325a5bd2e Update eslint 2021-08-30 10:02:39 +00:00
Renovate Bot 46569adf84 Update dependency husky to v7.0.2 2021-08-28 04:50:07 +00:00
Renovate Bot 1ada9125b7 Update dependency ts-jest to v27.0.5 2021-08-20 09:49:20 +00:00
Renovate Bot 0a260bd78e Update eslint to v4.29.2 2021-08-19 19:38:49 +00:00
Renovate Bot f9415e58ba Update dependency @types/jest to v27 2021-08-16 02:10:14 +00:00
Renovate Bot 1abd6285a2 Update Node.js to v14.17.5 2021-08-14 17:47:36 +00:00
Renovate Bot 93d1f2b3fb Update eslint 2021-08-13 14:48:41 +00:00
Renovate Bot 3baa30e4b8 Update dependency lint-staged to v11.1.2 2021-08-09 12:15:20 +00:00
Renovate Bot b76d2b89f8 Update eslint 2021-08-06 05:18:24 +00:00
21 changed files with 1532 additions and 1837 deletions
+1 -6
View File
@@ -7,12 +7,7 @@ module.exports = {
parserOptions: {
parser: '@typescript-eslint/parser',
},
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:vue/essential',
'prettier/vue',
'plugin:prettier/recommended',
],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:vue/essential', 'prettier'],
plugins: ['@typescript-eslint', 'vue'],
// add your custom rules here
rules: {
@@ -1,4 +1,4 @@
name: Test
name: Build
on: [push]
@@ -52,6 +52,8 @@ jobs:
steps:
# Setup
- uses: actions/checkout@v2
with:
token: ${{ secrets.MADSCI_BUILD_USER_TOKEN }} # allows commit of any fixes to trigger a new workflow run
- name: Use Node.js 12.x
uses: actions/setup-node@v2
with:
@@ -64,9 +66,17 @@ jobs:
name: node_modules.tar.zstd
- name: Unarchive node_modules
run: tar --use-compress-program "zstd -d --long=31" -xf node_modules.tar.zstd
# ESLint
- name: Lint source code
run: npm run lint
# Lint
- name: Run linters
uses: wearerequired/lint-action@v1
with:
prettier: true
eslint: true
eslint_args: "--ext '.ts,.js' --ignore-path '.gitignore' --ignore-pattern '.github/*'"
continue_on_error: false
auto_fix: true
git_name: Madbot
git_email: 64821814+madsci-bot@users.noreply.github.com
#
# build job
+1 -1
View File
@@ -1 +1 @@
14.17.4
14.17.6
+13
View File
@@ -0,0 +1,13 @@
# Ignore workflows
.github
# Node modules
node_modules
# Test results
test/.results
test/.coverage
# IDE
.idea
.vscode
+154 -1
View File
@@ -1,3 +1,156 @@
# 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 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 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!`);
}
}
}
```
## Credits
- `@SmartQuery()` decorator is based on the [vue-apollo-decorator](https://github.com/chanlito/vue-apollo-decorator)
package by chanlito, with some alteration to the types.
+1 -6
View File
@@ -1,11 +1,6 @@
{
"directory": "src/",
"exclude": [
"\\.(spec|e2e-spec)\\.ts$",
"src/cli/",
"src/database/",
"src/testing/"
],
"exclude": ["\\.(spec|e2e-spec)\\.ts$", "src/cli/", "src/database/", "src/testing/"],
"location": "all",
"structure": "flat",
"singleQuotes": true,
+15 -22
View File
@@ -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'],
};
+887 -1704
View File
File diff suppressed because it is too large Load Diff
+24 -20
View File
@@ -1,6 +1,6 @@
{
"name": "vue-apollo-smart-ops",
"version": "0.0.4",
"version": "0.1.0",
"description": "Create TypeScript-typed operation functions for your Vue Apollo queries and mutations.",
"author": "Madscience Ltd",
"license": "MIT",
@@ -15,11 +15,12 @@
"prebuild": "rimraf dist && npm run barrels:generate",
"build": "tsc -p tsconfig.build.json",
"postbuild": "cp package.json README.md dist/",
"format": "prettier --write .",
"lint": "eslint --ext .ts --ignore-path .gitignore .",
"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 +30,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 +41,38 @@
"vue-apollo": ">=3"
},
"devDependencies": {
"@types/jest": "26.0.24",
"@types/jest": "27.0.1",
"@types/lodash.isplainobject": "4.0.6",
"@types/lodash.mapvalues": "4.6.6",
"@typescript-eslint/eslint-plugin": "4.28.5",
"@typescript-eslint/parser": "4.28.5",
"@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.31.0",
"eslint-config-prettier": "7.2.0",
"eslint-plugin-import": "2.23.4",
"eslint": "7.32.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-import": "2.24.2",
"eslint-plugin-jest": "24.4.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-promise": "4.3.1",
"eslint-plugin-vue": "7.14.0",
"graphql": "15.5.1",
"husky": "7.0.1",
"jest": "27.0.6",
"jest-html-reporters": "2.1.6",
"lint-staged": "11.1.1",
"eslint-plugin-promise": "5.1.0",
"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.5.0",
"prettier": "2.2.1",
"prettier": "2.3.2",
"rimraf": "3.0.2",
"ts-jest": "27.0.4",
"typescript": "4.2.4",
"ts-jest": "27.0.5",
"typescript": "4.4.2",
"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"
+2 -6
View File
@@ -12,12 +12,8 @@
"prCreation": "not-pending",
"stabilityDays": 3,
"rebaseWhen": "conflicted",
"labels": [
"dependencies"
],
"reviewers": [
"sgarner"
],
"labels": ["dependencies"],
"reviewers": ["sgarner"],
"packageRules": [
{
"packagePatterns": ["eslint"],
+29 -18
View File
@@ -102,6 +102,29 @@ export class ApolloErrorProcessor<TApp = Vue, TContext = ApolloOperationContext>
return (this.constructor as typeof ApolloErrorProcessor).FriendlyMessages[errorCode] ?? errorMessage;
}
protected processErrorMessage(message: GraphQLError['message']): string {
if (typeof message === 'object') {
if (message.error != null) {
return message.error;
}
return 'Unknown error: ' + JSON.stringify(message);
}
return message;
}
protected normalizeError(error: GraphQLError | Error): Error {
if (isGraphQLError(error)) {
return {
...error,
message: this.processErrorMessage(error.message),
};
}
return error;
}
private processApolloError(error: ApolloError): ProcessedApolloError[] {
if (error.graphQLErrors != null && error.graphQLErrors.length > 0) {
// Successful request but with errors from the resolver
@@ -140,8 +163,8 @@ export class ApolloErrorProcessor<TApp = Vue, TContext = ApolloOperationContext>
// Unauthorized (not logged in, or not allowed) error
const processedError: UnauthorizedError = {
type: ApolloErrorType.UNAUTHORIZED_ERROR,
error,
message: error.message,
error: this.normalizeError(error),
message: this.processErrorMessage(error.message),
path: error.path,
};
@@ -154,8 +177,8 @@ export class ApolloErrorProcessor<TApp = Vue, TContext = ApolloOperationContext>
// User input validation error
const processedError: InputValidationError = {
type: ApolloErrorType.INPUT_VALIDATION_ERROR,
error,
message: error.message,
error: this.normalizeError(error),
message: this.processErrorMessage(error.message),
path: error.path,
invalidArgs: error.extensions.invalidArgs,
violations: error.extensions.validationErrors,
@@ -169,9 +192,9 @@ export class ApolloErrorProcessor<TApp = Vue, TContext = ApolloOperationContext>
// Other GraphQL resolver error - probably a bug
const processedError: ServerError = {
type: error.extensions?.code != null ? error.extensions.code : ApolloErrorType.SERVER_ERROR,
error,
error: this.normalizeError(error),
message: this.getFriendlyMessage('INTERNAL_SERVER_ERROR', this.processErrorMessage(error.message)),
path: error.path,
message: this.getFriendlyMessage('INTERNAL_SERVER_ERROR', error.message),
};
this.onServerError(processedError);
@@ -204,16 +227,4 @@ export class ApolloErrorProcessor<TApp = Vue, TContext = ApolloOperationContext>
return errors;
}
private processErrorMessage(message: GraphQLError['message']): string {
if (typeof message === 'object') {
if (message.error != null) {
return message.error;
}
return 'Unknown error: ' + JSON.stringify(message);
}
return message;
}
}
+285
View File
@@ -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),
}),
]),
}),
);
});
});
+18
View File
@@ -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;
});
}
+5
View File
@@ -0,0 +1,5 @@
/**
* @file Automatically generated by barrelsby.
*/
export * from './SmartQuery';
+1
View File
@@ -8,3 +8,4 @@ export * from './mutation';
export * from './query';
export * from './subscription';
export * from './types';
export * from './decorator/index';
+14 -15
View File
@@ -33,18 +33,17 @@ export type ApolloMutationClient<TResult, TVariables extends OperationVariables>
// Mutation function with typings
export type MutationOperationFunction<TResult, TVariables extends OperationVariables, TError = ApolloError> = (
app: VueAppWithApollo,
params: Omit<MutationOperationParams<TVariables, TError>, 'mutation'>,
params: Omit<MutationOperationParams<TResult, TVariables, TError>, 'mutation'>,
client?: ApolloMutationClient<TResult, TVariables>,
) => Promise<MutationResult<TResult>>;
// Parameters given to a MutationOperationFunction
export interface MutationOperationParams<
TResult,
TVariables extends OperationVariables,
TError = ApolloError,
TContext = ApolloOperationContext
> {
mutation: DocumentNode;
variables: TVariables;
TContext = ApolloOperationContext,
> extends MutationOptions<TResult, TVariables> {
context?: TContext;
onError?: ApolloOperationErrorHandlerFunction<TError>;
}
@@ -83,13 +82,13 @@ export async function mutateWithErrorHandling<
TResult,
TVariables extends OperationVariables,
TError,
TApp extends VueAppWithApollo = VueAppWithApollo
TApp extends VueAppWithApollo = VueAppWithApollo,
>(
app: TApp,
{ mutation, variables, onError, context }: MutationOperationParams<TVariables, TError>,
params: MutationOperationParams<TResult, TVariables, TError>,
client?: ApolloMutationClient<TResult, TVariables>,
): Promise<MutationResult<TResult>> {
const mutate =
const mutate: ApolloClientMutationFunction | ApolloComponentMutationFunction =
client === undefined
? app.$apollo.mutate.bind(app.$apollo)
: typeof client === 'function'
@@ -98,8 +97,8 @@ export async function mutateWithErrorHandling<
try {
const result = await mutate({
mutation,
variables: cleanInput(variables),
...params,
variables: params.variables != null ? cleanInput(params.variables) : undefined,
});
if (result == null) {
@@ -108,6 +107,7 @@ export async function mutateWithErrorHandling<
return { success: true, data: result.data };
} catch (error) {
const { onError, context } = params;
const errorHandlerResult: ApolloErrorHandlerResult | undefined =
onError != null ? onError(error, app, context) : undefined;
@@ -123,23 +123,22 @@ export function createMutationFunction<
TResult,
TVariables extends OperationVariables,
TError = ApolloError,
TApp extends VueAppWithApollo = VueAppWithApollo
TApp extends VueAppWithApollo = VueAppWithApollo,
>(
mutation: DocumentNode,
onError?: ApolloOperationErrorHandlerFunction<TError, TApp>,
): MutationOperationFunction<TResult, TVariables, TError> {
return (
app: TApp,
params: Omit<MutationOperationParams<TVariables, TError>, 'mutation'>,
params: Omit<MutationOperationParams<TResult, TVariables, TError>, 'mutation'>,
client?: ApolloMutationClient<TResult, TVariables>,
): Promise<MutationResult<TResult>> => {
return mutateWithErrorHandling(
app,
{
mutation,
variables: params.variables,
onError: params.onError ?? onError,
context: params.context,
onError,
...params,
},
client,
);
+57 -14
View File
@@ -1,39 +1,82 @@
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;
// added here pending upstream fix for missing types https://github.com/vuejs/vue-apollo/pull/1257
throttle?: number;
debounce?: number;
}
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,
@@ -54,8 +97,8 @@ export function createSmartQueryOptionsFunction<TResult, TVariables, TError = Ap
...options,
error:
// we have to override vue-apollo types because they are incorrect
((options.error as unknown) as ErrorHandler | undefined) ?? onError !== undefined
? ((defaultErrorHandlerFn as unknown) as ErrorHandler)
(options.error as unknown as ErrorHandler | undefined) ?? onError !== undefined
? (defaultErrorHandlerFn as unknown as ErrorHandler)
: undefined,
};
};
+5 -5
View File
@@ -8,7 +8,7 @@ export type VueApolloSmartSubscriptionErrorHandler<
TResult = any,
TVariables = OperationVariables,
TError = ApolloError,
TApp extends Vue = Vue
TApp extends Vue = Vue,
> = (
error: TError,
vm: TApp,
@@ -21,7 +21,7 @@ export type VueApolloSmartSubscriptionOptions<
TResult = any,
TVariables = OperationVariables,
TError = ApolloError,
TApp extends Vue = Vue
TApp extends Vue = Vue,
> = Omit<VueApolloSubscriptionDefinition<TVariables>, 'error'> & {
error?: VueApolloSmartSubscriptionErrorHandler<TResult, TVariables, TError, TApp>;
};
@@ -30,7 +30,7 @@ export function createSmartSubscriptionOptionsFunction<
TResult,
TVariables,
TError = ApolloError,
TApp extends Vue = Vue
TApp extends Vue = Vue,
>(
query: DocumentNode,
onError?: ApolloOperationErrorHandlerFunction<TError, TApp>,
@@ -57,8 +57,8 @@ export function createSmartSubscriptionOptionsFunction<
...options,
error:
// we have to override vue-apollo types because they are incorrect
((options.error as unknown) as ErrorHandler | undefined) ?? onError !== undefined
? ((defaultErrorHandlerFn as unknown) as ErrorHandler)
(options.error as unknown as ErrorHandler | undefined) ?? onError !== undefined
? (defaultErrorHandlerFn as unknown as ErrorHandler)
: undefined,
};
};
+2 -2
View File
@@ -26,7 +26,7 @@ export interface ApolloNetworkError extends Error {
};
}
export type GraphQLError = BaseGraphQLError & {
export type GraphQLError = Omit<BaseGraphQLError, 'message'> & {
message: string | Record<string, any>;
};
@@ -88,5 +88,5 @@ export interface ApolloErrorHandlerResult {
export type ApolloOperationErrorHandlerFunction<
TError = BaseApolloError,
TApp extends Vue = Vue,
TContext = ApolloOperationContext
TContext = ApolloOperationContext,
> = (error: TError, app: TApp, context?: TContext) => ApolloErrorHandlerResult;
View File
+4 -13
View File
@@ -15,18 +15,9 @@
"strictNullChecks": true,
"outDir": "./dist",
"baseUrl": "./",
"paths": {
},
"typeRoots": [
"./node_modules/@types",
"./@types"
]
"paths": {},
"types": ["@types/node", "@types/jest", "vue-apollo/types"]
},
"include":[
"src/**/*",
"test/**/*"
],
"exclude": [
"node_modules"
]
"include": ["src/**/*", "test/**/*"],
"exclude": ["node_modules"]
}