Skip to content

Commit

Permalink
refactor(graphql): abandon vue-apollo over apollo-client, Add eslint
Browse files Browse the repository at this point in the history
Avoid vue-apollo overhead by abandoning the package over direct calls to apollo-client; Add eslint
config; Fix race conditions

BREAKING CHANGE: Subscriptions are now called directly, removed vue-apollo components and Vue
component properties
  • Loading branch information
matrunchyk committed May 24, 2020
1 parent e64330a commit 4771b81
Show file tree
Hide file tree
Showing 13 changed files with 9,978 additions and 4,414 deletions.
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
test
*.d.ts
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"rules": {
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/ban-ts-ignore": 0
"@typescript-eslint/ban-ts-comment": 0
},
"overrides": [
{
Expand Down
5,256 changes: 4,713 additions & 543 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 13 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-oop",
"version": "0.7.7",
"version": "0.8.0",
"description": "A library based on Model-Repository patterns for Vue components. Usable for GraphQL and REST APIs.",
"keywords": [
"collections",
Expand Down Expand Up @@ -34,24 +34,29 @@
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"lint": "eslint '*/**/*.{ts,tsx}' --fix",
"test": "jest --config jest.config.json",
"test:watch": "jest -w",
"danger": "danger",
"publish-github": "npm publish --registry https://npm.pkg.github.com/@matrunchyk",
"release": "release-it",
"snyk-protect": "snyk protect",
"prepare": "npm run snyk-protect"
"prepare": "exit 0 || npm run snyk-protect"
},
"dependencies": {
"apollo-cache-inmemory": "^1.6.6",
"apollo-cache-persist": "^0.1.1",
"collect.js": "^4.25.0",
"get-graphql-schema": "^2.1.2",
"graphql": "^15.0.0",
"graphql-tag": "^2.10.2",
"lodash.camelcase": "^4.3.0",
"lodash.clonedeep": "^4.5.0",
"pusher-js": "^6.0.3",
"snyk": "^1.323.2",
"to-case": "^2.0.0",
"uuid": "^8.1.0"
"uuid": "^8.1.0",
"vue-cli-plugin-apollo": "^0.21.3"
},
"devDependencies": {
"@commitlint/config-conventional": "^8.3.4",
Expand All @@ -63,6 +68,8 @@
"@types/node": "^12.12.41",
"@types/uuid": "^8.0.0",
"@types/vue": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^3.0.0",
"@typescript-eslint/parser": "^3.0.0",
"apollo-client": "^2.6.10",
"apollo-link-http-common": "^0.2.16",
"codecov": "^3.7.0",
Expand All @@ -71,15 +78,15 @@
"cz-conventional-changelog": "^3.2.0",
"cz-gitmoji": "^0.0.7",
"danger": "^10.2.0",
"eslint": "^7.1.0",
"husky": "^4.2.5",
"jest": "^26.0.1",
"release-it": "^13.6.1",
"ts-jest": "^26.0.0",
"ts-node": "^8.10.1",
"tslib": "^2.0.0",
"typescript": "^3.9.3",
"vue": "^2.6.11",
"vue-apollo": "^3.0.3",
"tslib": "^2.0.0"
"vue": "^2.6.11"
},
"peerDependencies": {
"apollo-client": "^2.6.10",
Expand Down
20 changes: 4 additions & 16 deletions src/EventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ export default abstract class EventEmitter {

private _firedEvents: EventType[] = [];

public emit(type: string, payload?: unknown) {
public emit(type: string, payload?: unknown): boolean {
const event = this.createEvent(type, payload);

this._firedEvents[type] = event;

if (!(event.type in this._eventListeners)) {
return true;
return false;
}
const subscribers = this._eventListeners[event.type].slice();

Expand All @@ -35,9 +35,9 @@ export default abstract class EventEmitter {
subscriber.callback.call(this, event);
}
return true;
};
}

on(type, callback: (event: EventType) => void, config = {immediate: false}) {
public on(type: string, callback: (event: EventType) => void, config = { immediate: false }): void {
if (!(type in this._eventListeners)) {
this._eventListeners[type] = [];
}
Expand All @@ -60,18 +60,6 @@ export default abstract class EventEmitter {
;
}

/**
* @todo Implement the ideas below from https://basarat.gitbooks.io/typescript/content/docs/tips/typed-event.html
*/
once(): void {
}

off(): void {
}

pipe() {
}

private createEvent(type, payload?): EventType {
return {
type,
Expand Down
36 changes: 36 additions & 0 deletions src/graphql/CustomHeuristicFragmentMatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { HeuristicFragmentMatcher } from 'apollo-cache-inmemory';

let haveWarned = false;

export default class CustomHeuristicFragmentMatcher extends HeuristicFragmentMatcher {
match(idValue: any, typeCondition: any, context: any) {
const obj = context.store.get(idValue.id);

if (!obj) {
return false;
}

if (!obj.__typename) {
if (!haveWarned) {
console.warn(`You're using fragments in your queries, but either don't have the addTypename:
true option set in Apollo Client, or you are trying to write a fragment to the store without the __typename.
Please turn on the addTypename option and include __typename when writing fragments so that Apollo Client
can accurately match fragments.`);
console.warn(
'Could not find __typename on Fragment ',
typeCondition,
obj,
);
console.warn(`DEPRECATION WARNING: using fragments without __typename is
unsupported behavior and will be removed in future versions of Apollo client.
You should fix this and set addTypename to true now.`);
}

haveWarned = true;

return false;
}

return obj.__typename === typeCondition;
}
}
63 changes: 63 additions & 0 deletions src/graphql/PusherLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ApolloLink, FetchResult, Observable, Operation } from 'apollo-link';
import Pusher from 'pusher-js';

class PusherLink extends ApolloLink {
private pusher: Pusher;

constructor(options: any) {
super();
// Retain a handle to the Pusher client
this.pusher = options.pusher;
}

request(operation: Operation, forward: (operation: Operation) => Observable<FetchResult>) {
return new Observable(observer => {
// Check the result of the operation
forward(operation).subscribe({
next: data => {
// If the operation has the subscription extension, it's a subscription
const subscriptionChannel = this._getChannel(
{ data, operation },
);

if (subscriptionChannel) {
this._createSubscription(subscriptionChannel, observer);
} else {
// No subscription found in the response, pipe data through
observer.next(data);
observer.complete();
}
},
});
});
}

_getChannel({ data, operation }: { data: any, operation: any }) {
return !!data.extensions
&& !!data.extensions.lighthouse_subscriptions
&& !!data.extensions.lighthouse_subscriptions.channels
? data.extensions.lighthouse_subscriptions.channels[operation.operationName]
: null;
}

_createSubscription(subscriptionChannel: string, observer: any) {
const pusherChannel = this.pusher.subscribe(subscriptionChannel);
// Subscribe for more update

pusherChannel.bind('lighthouse-subscription', (payload: any) => {
if (!payload.more) {
// This is the end, the server says to unsubscribe
this.pusher.unsubscribe(subscriptionChannel);
observer.complete();
}
const { result } = payload;

if (result) {
// Send the new response to listeners
observer.next(result);
}
});
}
}

export default PusherLink;
55 changes: 55 additions & 0 deletions src/graphql/apolloClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { persistCache } from 'apollo-cache-persist';
import { createApolloClient } from 'vue-cli-plugin-apollo/graphql-client';
import Pusher from 'pusher-js';
import CustomHeuristicFragmentMatcher from './CustomHeuristicFragmentMatcher';
import PusherLink from './PusherLink';

Pusher.logToConsole = process.env.NODE_ENV !== 'production';

// Name of the localStorage item
const AUTH_TOKEN = 'accessToken';

// Http endpoint
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://127.0.0.1:3000/graphql';

const pusherLink = new PusherLink({
pusher: new Pusher(process.env.VUE_APP_PUSHER_KEY, {
cluster: process.env.VUE_APP_PUSHER_CLUSTER,
forceTLS: true,
authEndpoint: `${process.env.VUE_APP_HTTP_ENDPOINT}/graphql/subscriptions/auth`,
auth: {
params: null,
headers: {
authorization: `Bearer ${localStorage.getItem(AUTH_TOKEN)}`,
},
},
}),
});

const cache = new InMemoryCache({
// @ts-ignore
dataIdFromObject: result => (result.__typename && result.uuid ? `${result.__typename}:${result.uuid}` : defaultDataIdFromObject(result)),
fragmentMatcher: new CustomHeuristicFragmentMatcher(),
});

// noinspection JSIgnoredPromiseFromCall
persistCache({
cache,
// @ts-ignore
storage: window.localStorage,
debug: true,
});

const { apolloClient, wsClient } = createApolloClient({
httpEndpoint,
tokenName: AUTH_TOKEN,
link: pusherLink,
connectToDevTools: process.env.NODE_ENV !== 'production',
cache,
});

export {
apolloClient,
wsClient,
}
27 changes: 21 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface IVueOOPOptions {
schema?: DocumentNode;
schemaUrl?: string;
debug?: boolean;
createProvider?: Function;
}

// istanbul ignore next
Expand Down Expand Up @@ -61,6 +62,8 @@ export class VueOOPOptions implements IVueOOPOptions {
* @type {boolean}
*/
debug = false;

createProvider?: Function;
}

async function VueOOP<VueOOPOptions>(Vue: typeof _Vue, options?: VueOOPOptions): Promise<void> {
Expand All @@ -70,6 +73,7 @@ async function VueOOP<VueOOPOptions>(Vue: typeof _Vue, options?: VueOOPOptions):
schema: null,
schemaUrl: null,
debug: false,
createProvider: null,
...options,
} as IVueOOPOptions;

Expand All @@ -80,18 +84,29 @@ async function VueOOP<VueOOPOptions>(Vue: typeof _Vue, options?: VueOOPOptions):
.then(parse.bind(null));
}

registry.set('Config', config);
if (config.graphql) {
// Vue.use(VueApollo);
}

Vue.prototype.$registry = registry;
registry.set('Config', config);

Vue.mixin({
beforeCreate() {
// istanbul ignore else
// If a non-root component, or there's already the Vue instance set in Registry,
// If there's already the Vue instance set in Registry,
// return, as we do need a Vue instance with $apollo in it.
if (this.$parent || registry.has('Vue')) return;

registry.set('Vue', this);
if (registry.has('Vue')) return;

Object.defineProperty(Vue.prototype, '$registry', {
get() {
if (!this.$_registry) {
this.$_registry = registry;
}
return this.$_registry;
},
});

registry.set('Vue', this.$root);
},
});
}
Expand Down
Loading

0 comments on commit 4771b81

Please sign in to comment.