Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mercurius-cache

Package Overview
Dependencies
Maintainers
2
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mercurius-cache - npm Package Compare versions

Comparing version 0.10.0 to 0.11.0

lib/validation.js

104

index.js

@@ -5,13 +5,13 @@ 'use strict'

const { Cache } = require('async-cache-dedupe')
const { validateOpts } = require('./lib/validation')
module.exports = fp(async function (app, { all, policy, ttl, cacheSize, skip, storage, onHit, onMiss, onSkip, ...other }) {
if (typeof policy !== 'object' && !all) {
throw new Error('policy must be an object')
} else if (all && policy) {
throw new Error('policy and all options are exclusive')
module.exports = fp(async function (app, opts) {
validateOpts(opts)
let { all, policy, ttl, cacheSize, skip, storage, onHit, onMiss, onSkip, logInterval, logReport } = opts
if (!logReport) {
logReport = defaultLogReport
}
// TODO validate mercurius is already registered
// TODO validate policy
onHit = onHit || noop

@@ -22,7 +22,14 @@ onMiss = onMiss || noop

let cache = null
let logTimer = null
let cacheReport = null
const cacheReportingEnabled = logInterval && ((policy && policy.Query) || all)
if (cacheReportingEnabled) {
initCacheReport()
}
app.graphql.cache = {
refresh () {
buildCache()
setupSchema(app.graphql.schema, policy, all, cache, skip, storage, onHit, onMiss, onSkip)
setupSchema(app.graphql.schema, policy, all, cache, skip, storage, onHit, onMiss, onSkip, cacheReport)
},

@@ -32,2 +39,4 @@

cache.clear()
logReport(cacheReport)
clearCacheReport(cacheReport)
}

@@ -38,8 +47,17 @@ }

app.graphql.cache.refresh()
if (cacheReportingEnabled) {
logTimer = setInterval(logAndClearCacheReport, logInterval * 1000, cacheReport).unref()
}
})
app.addHook('onClose', () => {
if (logTimer) {
clearInterval(logTimer)
}
})
// Add hook to regenerate the resolvers when the schema is refreshed
app.graphql.addHook('onGatewayReplaceSchema', async (instance, schema) => {
buildCache()
setupSchema(schema, policy, all, cache, skip, storage, onHit, onMiss, onSkip)
setupSchema(schema, policy, all, cache, skip, storage, onHit, onMiss, onSkip, cacheReport)
})

@@ -53,6 +71,43 @@

}
function initCacheReport () {
cacheReport = {}
const schema = app.graphql.schema
const fields = all ? Object.keys(schema.getQueryType().getFields()) : Object.keys(policy.Query)
for (const field of fields) {
const name = 'Query.' + field
cacheReport[name] = {}
cacheReport[name].hits = 0
cacheReport[name].misses = 0
}
}
function clearCacheReport (cacheReport) {
if (!cacheReport) return
for (const item of Object.keys(cacheReport)) {
cacheReport[item].hits = 0
cacheReport[item].misses = 0
}
}
function defaultLogReport (cacheReport) {
app.log.info({ cacheReport }, 'mercurius-cache report')
}
function logAndClearCacheReport (cacheReport) {
logReport(cacheReport)
clearCacheReport(cacheReport)
}
}, {
fastify: '^3.x',
dependencies: ['mercurius']
})
function setupSchema (schema, policy, all, cache, skip, storage, onHit, onMiss, onSkip) {
function setupSchema (schema, policy, all, cache, skip, storage, onHit, onMiss, onSkip, cacheReport) {
const schemaTypeMap = schema.getTypeMap()
let queryKeys = policy ? Object.keys(policy.Query) : []
for (const schemaType of Object.values(schemaTypeMap)) {

@@ -69,6 +124,8 @@ const fieldPolicy = all || policy[schemaType]

if (all || policy) {
// validate schema vs query values
queryKeys = queryKeys.filter(key => key !== fieldName)
// Override resolvers for caching purposes
if (typeof field.resolve === 'function') {
const originalFieldResolver = field.resolve
field.resolve = makeCachedResolver(schemaType.toString(), fieldName, cache, originalFieldResolver, policy, skip, storage, onHit, onMiss, onSkip)
field.resolve = makeCachedResolver(schemaType.toString(), fieldName, cache, originalFieldResolver, policy, skip, storage, onHit, onMiss, onSkip, cacheReport)
}

@@ -79,5 +136,6 @@ }

}
if (queryKeys.length) { throw new Error(`Query does not match schema: ${queryKeys}`) }
}
function makeCachedResolver (prefix, fieldName, cache, originalFieldResolver, policy, skip, storage, onHit, onMiss, onSkip) {
function makeCachedResolver (prefix, fieldName, cache, originalFieldResolver, policy, skip, storage, onHit, onMiss, onSkip, cacheReport) {
const name = prefix + '.' + fieldName

@@ -88,4 +146,19 @@ onHit = onHit.bind(null, prefix, fieldName)

let onCacheHit = null
let onCacheMiss = null
if (cacheReport) {
onCacheHit = () => {
cacheReport[name].hits++
onHit()
}
onCacheMiss = () => {
cacheReport[name].misses++
onMiss()
}
}
cache.define(name, {
onHit,
onHit: onCacheHit || onHit,
ttl: policy && policy.ttl,

@@ -126,3 +199,4 @@ cacheSize: policy && policy.cacheSize,

}
onMiss()
onCacheMiss ? onCacheMiss() : onMiss()
const res = await originalFieldResolver(self, arg, ctx, info)

@@ -129,0 +203,0 @@ if (storage) {

3

package.json
{
"name": "mercurius-cache",
"version": "0.10.0",
"version": "0.11.0",
"description": "Cache the results of your GraphQL resolvers, for Mercurius",

@@ -32,2 +32,3 @@ "main": "index.js",

"snazzy": "^9.0.0",
"split2": "^4.1.0",
"standard": "^16.0.3",

@@ -34,0 +35,0 @@ "tap": "^15.0.9",

@@ -240,2 +240,25 @@ # mercurius-cache

- **logInterval**
This option enables cache report with hit/miss count for all queries specified in the policy.
The value of the interval is in *seconds*.
Example
```js
logInterval: 3
```
- **logReport**
custom function for logging cache hits/misses. called every `logInterval` seconds when the cache report is logged.
Example
```js
logReport (cacheReport) {
console.log(`Cache report - ${cacheReport}`)
}
```
## Benchmarks

@@ -242,0 +265,0 @@

@@ -671,2 +671,99 @@ 'use strict'

equal(hitCount, 0)
});
[
{
title: 'using all option as string',
cacheConfig: { all: 'true' },
expect: 'all must be an boolean'
},
{
title: 'using ttl option as string',
cacheConfig: { ttl: '10' },
expect: 'ttl must be a number'
},
{
title: 'using cacheSize option as string',
cacheConfig: { cacheSize: '1024' },
expect: 'cacheSize must be a number'
},
{
title: 'using onHit option as string',
cacheConfig: { onHit: 'not a function' },
expect: 'onHit must be a function'
},
{
title: 'using onMiss option as string',
cacheConfig: { onMiss: 'not a function' },
expect: 'onMiss must be a function'
},
{
title: 'using onSkip option as string',
cacheConfig: { onSkip: 'not a function' },
expect: 'onSkip must be a function'
},
{
title: 'using policy option as string',
cacheConfig: { policy: 'not an object' },
expect: 'policy must be an object'
}
].forEach(useCase => {
test(useCase.title, async (t) => {
t.plan(1)
const app = fastify()
app.register(mercurius)
await t.rejects(app.register(cache, useCase.cacheConfig), useCase.expect)
})
})
test('Unmatched schema for Query', async ({ rejects, teardown }) => {
const app = fastify()
teardown(app.close.bind(app))
const schema = `
type Query {
add(x: Int, y: Int): Int
}
`
const resolvers = {
Query: {
async add (_, { x, y }) {
await immediate()
return x + y
}
}
}
app.register(mercurius, {
schema,
resolvers
})
app.register(cache, {
policy: {
Query: {
add: true,
foo: 'bar'
}
}
})
await Promise.all([
query(),
query()
])
async function query () {
const query = '{ add(x: 2, y: 2) }'
await rejects(app.inject({
method: 'POST',
url: '/graphql',
body: {
query
}
}), 'Query does not match schema: foo')
}
})

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc