diff --git a/README.md b/README.md index b1a7d2b..15a0c3e 100644 --- a/README.md +++ b/README.md @@ -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(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({ + apollo: { + todos: useTodoListQuery({ + 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({ + 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(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 { + 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. diff --git a/jest.config.js b/jest.config.js index deeeccb..d62a2e1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,24 +1,17 @@ +const { pathsToModuleNameMapper } = require('ts-jest/utils'); +const { compilerOptions } = require('./tsconfig'); + module.exports = { - moduleNameMapper: { - '^@/(.*)$': '/$1', - '^~/(.*)$': '/$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: ['/components/**/*.vue', '/pages/**/*.vue'], - reporters: [ - 'default', - [ - 'jest-html-reporters', - { - filename: 'test/.results/test-results.html', - }, - ], - ], + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src/', '/test/'], + testRegex: '\\.(spec|e2e-spec)\\.ts$', + testPathIgnorePatterns: ['/node_modules/'], + testTimeout: 20000, + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { + prefix: '/', + }), + coveragePathIgnorePatterns: ['/node_modules/', '/test/'], + coverageDirectory: '/test/.coverage', + reporters: ['default'], }; diff --git a/package-lock.json b/package-lock.json index e4e74df..d20b055 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vue-apollo-smart-ops", - "version": "0.0.4", + "version": "0.1.0-alpha.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1552,18 +1552,6 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, - "@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -2054,77 +2042,6 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "babel-jest": { "version": "27.1.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.1.0.tgz", @@ -2156,15 +2073,6 @@ } } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-plugin-istanbul": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", @@ -2190,28 +2098,6 @@ "@types/babel__traverse": "^7.0.6" } }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, "babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -2242,95 +2128,6 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2358,15 +2155,6 @@ } } }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "boxen": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz", @@ -2718,12 +2506,6 @@ "wrap-ansi": "^5.1.0" } }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -2862,12 +2644,6 @@ "safe-buffer": "~5.1.1" } }, - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, "cosmiconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", @@ -2912,18 +2688,6 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, "cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -2964,16 +2728,6 @@ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true }, - "deasync": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.21.tgz", - "integrity": "sha512-kUmM8Y+PZpMpQ+B4AuOW9k2Pfx/mSupJtxOsLzmnHY2WqZUYRFccFn2RhzPAqt3Xb+sorK/badW2D4zNzqZz5w==", - "dev": true, - "requires": { - "bindings": "^1.5.0", - "node-addon-api": "^1.7.1" - } - }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -3013,12 +2767,6 @@ "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", "dev": true }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, "decompress-response": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", @@ -3058,12 +2806,6 @@ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3888,15 +3630,6 @@ "tmp": "^0.0.33" } }, - "extract-from-css": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/extract-from-css/-/extract-from-css-0.4.4.tgz", - "integrity": "sha1-HqffLnx8brmSL6COitrqSG9vj5I=", - "dev": true, - "requires": { - "css": "^2.1.0" - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3971,12 +3704,6 @@ "flat-cache": "^3.0.4" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3986,24 +3713,6 @@ "to-regex-range": "^5.0.1" } }, - "find-babel-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", - "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", - "dev": true, - "requires": { - "json5": "^0.5.1", - "path-exists": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - } - } - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -4040,26 +3749,6 @@ "mime-types": "^2.1.12" } }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4258,6 +3947,23 @@ "integrity": "sha512-dZjLPWNQqYv0dqV2RNbiFed0LtSp6yd4jchsDGnuhDKa9OQHJYCfovaOEvY91w9gqbYO7Se9LKDTl3xxYva/3w==", "dev": true }, + "graphql-tag": { + "version": "2.12.5", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.5.tgz", + "integrity": "sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "dev": true + } + } + }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -4750,15 +4456,6 @@ "side-channel": "^1.0.4" } }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5754,16 +5451,6 @@ } } }, - "jest-html-reporters": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/jest-html-reporters/-/jest-html-reporters-2.1.6.tgz", - "integrity": "sha512-L81yXCctu0clmRl2rDeq5nWdH6Sy1U0IJgf+wy6wUTPcCmfg3JIfJnTug0mWSgQqtk0I5IMZ7Z+c4317uMNgMw==", - "dev": true, - "requires": { - "fs-extra": "^9.0.1", - "open": "^8.0.3" - } - }, "jest-jasmine2": { "version": "27.1.0", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.1.0.tgz", @@ -6845,24 +6532,6 @@ "minimist": "^1.2.5" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, "keyv": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", @@ -7516,15 +7185,6 @@ } } }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -7898,22 +7558,6 @@ } } }, - "node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true - }, - "node-cache": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.1.tgz", - "integrity": "sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A==", - "dev": true, - "requires": { - "clone": "2.x", - "lodash": "^4.17.15" - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -8339,17 +7983,6 @@ "mimic-fn": "^2.1.0" } }, - "open": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/open/-/open-8.0.8.tgz", - "integrity": "sha512-3XmKIU8+H/TVr8wB8C4vj0z748+yBydSvtpzZVS6vQ1dKNHB6AiPbhaoG+89zb80717GPk9y/7OvK0R6FXkNmQ==", - "dev": true, - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -9106,12 +8739,6 @@ "strip-indent": "^3.0.0" } }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", @@ -9187,12 +8814,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "responselike": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", @@ -9400,19 +9021,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -9423,12 +9031,6 @@ "source-map": "^0.6.0" } }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -9843,26 +9445,6 @@ } } }, - "tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "requires": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, "tsconfig-paths": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", @@ -10022,12 +9604,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -10156,6 +9732,13 @@ } } }, + "vue-class-component": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz", + "integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==", + "dev": true, + "optional": true + }, "vue-eslint-parser": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.10.0.tgz", @@ -10196,87 +9779,10 @@ } } }, - "vue-jest": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.7.tgz", - "integrity": "sha512-PIOxFM+wsBMry26ZpfBvUQ/DGH2hvp5khDQ1n51g3bN0TwFwTy4J85XVfxTRMukqHji/GnAoGUnlZ5Ao73K62w==", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", - "chalk": "^2.1.0", - "deasync": "^0.1.15", - "extract-from-css": "^0.4.4", - "find-babel-config": "^1.1.0", - "js-beautify": "^1.6.14", - "node-cache": "^4.1.1", - "object-assign": "^4.1.1", - "source-map": "^0.5.6", - "tsconfig": "^7.0.0", - "vue-template-es2015-compiler": "^1.6.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "vue-template-es2015-compiler": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", - "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", + "vue-property-decorator": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz", + "integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==", "dev": true }, "w3c-hr-time": { diff --git a/package.json b/package.json index 70e87b1..f5641a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-apollo-smart-ops", - "version": "0.0.4", + "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", @@ -56,9 +59,9 @@ "eslint-plugin-promise": "4.3.1", "eslint-plugin-vue": "7.17.0", "graphql": "15.5.2", + "graphql-tag": "2.12.5", "husky": "7.0.2", "jest": "27.1.0", - "jest-html-reporters": "2.1.6", "lint-staged": "11.1.2", "mkdirp": "1.0.4", "np": "7.5.0", @@ -68,7 +71,8 @@ "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" diff --git a/src/decorator/SmartQuery.spec.ts b/src/decorator/SmartQuery.spec.ts new file mode 100644 index 0000000..cb42e6f --- /dev/null +++ b/src/decorator/SmartQuery.spec.ts @@ -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(gql` + query Todos($skip: Int, $limit: Int) { + todos(skip: $skip, limit: $limit) { + id + title + } + } + `); + + @Component({}) + class TodoList extends Vue { + @SmartQuery( + useTodoListQuery({ + 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({ + 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({ + 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) {}, + 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), + }), + ]), + }), + ); + }); +}); diff --git a/src/decorator/SmartQuery.ts b/src/decorator/SmartQuery.ts new file mode 100644 index 0000000..b7cefb4 --- /dev/null +++ b/src/decorator/SmartQuery.ts @@ -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( + options: TApp extends Vue ? VueApolloSmartQueryOptions : DocumentNode, +): VueDecorator { + return createDecorator((componentOptions: any, k: string) => { + componentOptions.apollo = componentOptions.apollo || {}; + componentOptions.apollo[k] = options; + }); +} diff --git a/src/decorator/index.ts b/src/decorator/index.ts new file mode 100644 index 0000000..3baa3dd --- /dev/null +++ b/src/decorator/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './SmartQuery'; diff --git a/src/index.ts b/src/index.ts index 2e52d57..4acb9e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,3 +8,4 @@ export * from './mutation'; export * from './query'; export * from './subscription'; export * from './types'; +export * from './decorator/index'; diff --git a/src/query.ts b/src/query.ts index 19b6cc8..10475e6 100644 --- a/src/query.ts +++ b/src/query.ts @@ -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 extends (...args: infer A) => infer B ? (this: T, ...args: A) => B : F; + +type OverrideAllThis = { + [key in keyof O]: OverrideThis; +}; + +type SubscribeToMoreOptionsPatched = OverrideAllThis< + Omit, 'updateQuery' | 'variables'>, + TComponent +> & { + variables?: (this: TComponent) => any; + updateQuery?: UpdateQueryFn; // TODO: How should we pass subscript data & variables types? +}; + +type UpdateQueryFn = ( + this: TComponent, + previousQueryResult: TResult, + options: { + subscriptionData: { + data: TSubscriptionData; + }; + variables?: TSubscriptionVariables; + }, +) => TResult; + +export interface VueApolloQueryDefinitionPatched + extends OverrideAllThis< + Omit, 'subscribeToMore' | 'variables' | 'loadingKey'>, + TComponent + > { + variables?: ((this: TComponent) => TVariables) | TVariables; + subscribeToMore?: + | SubscribeToMoreOptionsPatched + | Array>; + 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, + options: VueApolloSmartQueryOptions, ) => 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, 'subscribeToMore' | 'error'> & { - error?: VueApolloSmartQueryErrorHandler; + TComponent extends Vue = Vue +> = VueApolloQueryDefinitionPatched & { + error?: VueApolloSmartQueryErrorHandler; }; +export type VueApolloSmartQueryOptionsFunction = < + TComponent extends Vue = TApp +>( + options?: Partial, 'query'>>, +) => VueApolloSmartQueryOptions; + export function createSmartQueryOptionsFunction( query: DocumentNode, onError?: ApolloOperationErrorHandlerFunction, -): ( - options?: Partial>, -) => VueApolloQueryDefinition { +): VueApolloSmartQueryOptionsFunction { return (options = {}) => { const defaultErrorHandlerFn: VueApolloSmartQueryErrorHandler = ( error: TError, diff --git a/test/.gitkeep b/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json index 646ed05..4c3becc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,9 +17,10 @@ "baseUrl": "./", "paths": { }, - "typeRoots": [ - "./node_modules/@types", - "./@types" + "types": [ + "@types/node", + "@types/jest", + "vue-apollo/types" ] }, "include":[