Skip to content

Commit

Permalink
feat(browser): adjusted structure to be browser compatible.
Browse files Browse the repository at this point in the history
BREAKING CHANGE: removed support for `redis` library, added `ioredis` instead
  • Loading branch information
BowlingX committed Jan 3, 2020
1 parent 39a0d22 commit 35317c9
Show file tree
Hide file tree
Showing 11 changed files with 2,767 additions and 2,336 deletions.
2 changes: 2 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[ignore]
<PROJECT_ROOT>/node_modules/graphql/utilities/buildASTSchema.js.flow
<PROJECT_ROOT>/node_modules/graphql/type/scalars.js.flow
<PROJECT_ROOT>/node_modules/graphql/utilities/extendSchema.js.flow
<PROJECT_ROOT>/dist/.*

[include]
./src
Expand Down
24 changes: 13 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@
"eslint-plugin-flowtype": "^3.2.1",
"eslint-plugin-import": "^2.14.0",
"express": "^4.17.1",
"flow-bin": "^0.94.0",
"flow-bin": "^0.114.0",
"flow-copy-source": "^2.0.2",
"rimraf": "^2.6.3",
"semantic-release": "^15.13.3",
"redis": "^2.8.0"
"semantic-release": "^15.14.0"
},
"scripts": {
"prepare": "yarn run build:clean && yarn run build:lib && yarn run build:flow",
Expand All @@ -55,16 +54,19 @@
"@semantic-release/git"
]
},
"peerDependencies": {
"redis": "^2"
"optionalDependencies": {
"ioredis": "^4"
},
"dependencies": {
"@babel/polyfill": "^7.2.5",
"apollo-link": "^1.2.6",
"apollo-utilities": "^1.0.27",
"graphql": "^14.1.1",
"http-proxy-middleware": "^0.19.1",
"iltorb": "^2.4.2",
"@babel/polyfill": "^7.7.0",
"apollo-link": "^1.2.13",
"apollo-utilities": "^1.3.3",
"graphql": "^14.5.8",
"http-proxy-middleware": "^0.20.0",
"iltorb": "^2.4.4",
"lodash": "^4.17.11"
},
"engines": {
"node": ">=8"
}
}
2 changes: 1 addition & 1 deletion src/caches/inmemory.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow

import { Cache } from './types'
import { didTimeout } from '../utils'
import { didTimeout } from '../utils-browser-only'

// Simple in memory implementation using `Map`

Expand Down
39 changes: 10 additions & 29 deletions src/caches/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,20 @@ export class RedisCache implements Cache<string, Object> {
this.client = client
}

delete(key: string): Promise<boolean> {
return new Promise((resolve, reject) => {
this.client.del(key, (err, response) => {
if (err) {
return reject(err)
}
resolve(response === 1)
})
})
async delete(key: string): Promise<boolean> {
const result = await this.client.del(key)
return result === 1
}

get(key: string): Promise<?Object> {
return new Promise((resolve, reject) => {
this.client.get(key, (err, value) => {
if (err) {
return reject(err)
}
if (value) {
resolve(JSON.parse(value.toString()))
}
return resolve(null)
})
})
async get(key: string): Promise<?Object> {
const result = await this.client.get(key)
if (result) {
return JSON.parse(result.toString())
}
return null
}

set(key: string, value: Object, timeout: number): Promise<Cache<string, Object>> {
return new Promise((resolve, reject) => {
this.client.set(key, JSON.stringify(value), 'EX', timeout, err => {
if (err) {
return reject(err)
}
resolve(this)
})
})
return this.client.set(key, JSON.stringify(value), 'EX', timeout)
}
}
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { proxyCacheLink } from './proxyCacheLink'
export { proxyCacheMiddleware } from './proxyCacheMiddleware'
export { DIRECTIVE, removeCacheDirective } from './utils'
export { DIRECTIVE, removeCacheDirective } from './utils-browser-only'

export { InMemoryCache } from './caches/inmemory'
export { RedisCache } from './caches/redis'
Expand Down
10 changes: 8 additions & 2 deletions src/proxyCacheLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import {
Observable
} from 'apollo-link'
import { hasDirectives } from 'apollo-utilities'
import { calculateArguments, DIRECTIVE, errorOnGet, errorOnSet, removeCacheDirective } from './utils'
import type { CacheKeyModifier } from './utils'
import {
calculateArguments,
DIRECTIVE,
errorOnGet,
errorOnSet,
removeCacheDirective,
type CacheKeyModifier
} from './utils-browser-only'
import type { Cache } from './caches/types'

export const proxyCacheLink = (queryCache: Cache<string, Object>, cacheKeyModifier: CacheKeyModifier) => {
Expand Down
5 changes: 3 additions & 2 deletions src/proxyCacheMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import proxy from 'http-proxy-middleware'
import { parse } from 'graphql'
import { print } from 'graphql/language/printer'
import { hasDirectives } from 'apollo-utilities'
import { calculateArguments, decode, DIRECTIVE, errorOnGet, errorOnSet, removeCacheDirective } from './utils'
import type { CacheKeyModifier } from './utils'
import { decode } from './utils'
import type { Cache } from './caches/types'
import { calculateArguments, DIRECTIVE, removeCacheDirective, errorOnGet,
errorOnSet, type CacheKeyModifier } from './utils-browser-only'

const CACHE_HEADER = 'X-Proxy-Cached'

Expand Down
78 changes: 78 additions & 0 deletions src/utils-browser-only.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// @flow

import type { DocumentNode } from 'graphql'
import { checkDocument, cloneDeep } from 'apollo-utilities'
import _get from 'lodash/get'

export function removeDirectivesFromQuery(doc: DocumentNode, directive: string) {
const docClone = cloneDeep(doc)
docClone.definitions.forEach((definition: Object) => {
definition.directives = definition.directives.filter(d => d.name.value !== directive)
})
return docClone
}

const removed = new Map()

export function removeCacheDirective(
query: DocumentNode,
): DocumentNode {
const cached = removed.get(query)
if (cached) return cached

checkDocument(query)
const docClone = removeDirectivesFromQuery(
query,
'cache'
)
removed.set(query, docClone)
return docClone
}

export function getDirectiveArgumentsAsObject(doc: DocumentNode, directive: string) {
return _get(doc.definitions, '0.directives', [])
.filter(v => v.name.value === directive)
.reduce((next, v) => {
return {
...v.arguments.reduce((next, v) => (
{ [v.name.value]: v.value.value ||
(v.value.values ? v.value.values.map(v => v.value) : undefined), ...next }), {}),
...next
}
}, {})
}

export const DIRECTIVE = 'cache'

export type CacheKeyModifier = (?string, ?Object, ?Object) => ?string

export const didTimeout = (timeout: number, time: number) =>
timeout > 0 && ((time + (Number(timeout) * 1000)) < Number(new Date()))

export const calculateArguments =
(query: DocumentNode, variables: ?Object, cacheKeyModifier: ?CacheKeyModifier, context: Object) => {
const { id, timeout, modifier } = getDirectiveArgumentsAsObject(query, DIRECTIVE)
let thisId = modifier ? modifier.reduce((next, path) => {
return `${next}.${_get(variables, path, '')}`
}, id) : id

if (cacheKeyModifier) {
thisId = cacheKeyModifier(thisId, variables, context)
}

if (!thisId) {
throw new Error(`@${DIRECTIVE} directive requires a unique id.`)
}

return { id: thisId, timeout, modifier }
}

export const errorOnSet = (e: Object) => {
// eslint-disable-next-line no-console
console.error('[CACHE] Cache implementation threw Exception on `set`', e)
}

export const errorOnGet = (e: Object) => {
// eslint-disable-next-line no-console
console.error('[CACHE] Cache implementation threw Exception on `get`', e)
}
81 changes: 2 additions & 79 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,8 @@
// @flow

import type { DocumentNode } from 'graphql'
import { checkDocument, cloneDeep } from 'apollo-utilities'
import _get from 'lodash/get'
import { decompressStream } from 'iltorb'
import zlib from 'zlib'

export function removeDirectivesFromQuery(doc: DocumentNode, directive: string) {
const docClone = cloneDeep(doc)
docClone.definitions.forEach((definition: Object) => {
definition.directives = definition.directives.filter(d => d.name.value !== directive)
})
return docClone
}

const removed = new Map()

export function removeCacheDirective(
query: DocumentNode,
): DocumentNode {
const cached = removed.get(query)
if (cached) return cached

checkDocument(query)
const docClone = removeDirectivesFromQuery(
query,
'cache'
)
removed.set(query, docClone)
return docClone
}

export function getDirectiveArgumentsAsObject(doc: DocumentNode, directive: string) {
return _get(doc.definitions, '0.directives', [])
.filter(v => v.name.value === directive)
.reduce((next, v) => {
return {
...v.arguments.reduce((next, v) => (
{ [v.name.value]: v.value.value ||
(v.value.values ? v.value.values.map(v => v.value) : undefined), ...next }), {}),
...next
}
}, {})
}

export const DIRECTIVE = 'cache'

export type CacheKeyModifier = (?string, ?Object, ?Object) => ?string

export const didTimeout = (timeout: number, time: number) =>
timeout > 0 && ((time + (Number(timeout) * 1000)) < Number(new Date()))

export const calculateArguments =
(query: DocumentNode, variables: ?Object, cacheKeyModifier: ?CacheKeyModifier, context: Object) => {
const { id, timeout, modifier } = getDirectiveArgumentsAsObject(query, DIRECTIVE)
let thisId = modifier ? modifier.reduce((next, path) => {
return `${next}.${_get(variables, path, '')}`
}, id) : id

if (cacheKeyModifier) {
thisId = cacheKeyModifier(thisId, variables, context)
}

if (!thisId) {
throw new Error(`@${DIRECTIVE} directive requires a unique id.`)
}

return { id: thisId, timeout, modifier }
}

export async function stream(data: Object) {
const thisBuffer = []
return new Promise((resolve, reject) => {
Expand All @@ -82,6 +16,7 @@ export async function stream(data: Object) {
})
}

// $FlowFixMe: ignore, supported from node v11
const supportsBrotli = typeof zlib.createBrotliDecompress === 'function'

export async function decode(response: Object) {
Expand All @@ -92,8 +27,8 @@ export async function decode(response: Object) {
} else if (encoding === 'deflate') {
decoder = zlib.createInflate()
} else if (encoding === 'br') {
// $FlowFixMe: ignore, supported from node v11
if (supportsBrotli) {
// $FlowFixMe: ignore, supported from node v11
decoder = zlib.createBrotliDecompress()
} else {
decoder = decompressStream()
Expand All @@ -105,15 +40,3 @@ export async function decode(response: Object) {
}
return await stream(response)
}


export const errorOnSet = (e: Object) => {
// eslint-disable-next-line no-console
console.error('[CACHE] Cache implementation threw Exception on `set`', e)
}

export const errorOnGet = (e: Object) => {
// eslint-disable-next-line no-console
console.error('[CACHE] Cache implementation threw Exception on `get`', e)
}

6 changes: 2 additions & 4 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
import express from 'express'
import { proxyCacheMiddleware } from '../src/index'
import bodyParser from 'body-parser'
import { InMemoryCache } from '../src/caches/inmemory'
import { RedisCache } from '../src/caches/redis'
import redis from 'redis'
import Redis from 'ioredis'

const app = express()
const queryCache = new InMemoryCache()

const client = redis.createClient()
const client = new Redis()

const redisCache = new RedisCache(client)

Expand Down
Loading

0 comments on commit 35317c9

Please sign in to comment.