diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 5698df2..78c496d 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -33,7 +33,8 @@ module.exports = { "installation", "initialization", "example-usage", - "configuration" + "configuration", + "caching" ] } ], diff --git a/docs/guide/caching.md b/docs/guide/caching.md new file mode 100644 index 0000000..2149166 --- /dev/null +++ b/docs/guide/caching.md @@ -0,0 +1,43 @@ +# Caching + +Out of the box, Joodle provides support for [RFC 7234](https://httpwg.org/specs/rfc7234.html) compliant HTTP caching. Fresh cache entries are served directly from the cache, and stale entries are revalidated with `If-None-Match`/`If-Modified-Since` headers. + +## Supported Cache Stores + +Joodle uses Keyv internally to support a wide range of storage adapters, including anything that implements the native JavaScript [Map API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). + +This means that you can use an in-memory store: + +```js +... +const inMemoryStore = new Map(); + +const joodle = new Joodle( + ..., + { + ..., + cache: inMemoryStore + } +); +``` + +Or you can use a more persistent store such as Redis (using [@keyv/redis](https://www.npmjs.com/package/@keyv/redis)): + +```bash +$ npm install @keyv/redis +``` + +```js +... +const KeyvRedis = require("@keyv/redis"); + +const redis = new KeyvRedis("redis://user:pass@localhost:6379"); + +const joodle = new Joodle({ + ..., + { + ..., + cache: redis + } +}) +``` diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 4c4d424..7b75c8b 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -28,7 +28,8 @@ new Joodle( { timeout: 2000, retries: 5, - rejectInvalidSSL: false + rejectInvalidSSL: false, + cache: undefined } ); ``` @@ -39,6 +40,8 @@ The `retries` option indicates how many times the client should retry a request The `rejectInvalidSSL` option indicates whether the client should reject invalid SSL certificates or not. By default, the client will reject invalid SSL certificates. +The `cache` option allows you to enable HTTP response caching. By default, the client will not cache any responses. See the [Caching](/guide/caching.html) section for more information. + ::: danger You should only set the `rejectInvalidSSL` option to `true` when connecting to local Moodle instances (`baseURL: "https://localhost"`). There are severe security implications when accepting invalid SSL certificates from remote Moodle sites. ::: diff --git a/package.json b/package.json index dc823f3..966c0a8 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ }, "dependencies": { "@babel/runtime": "^7.9.6", + "cacheable-request": "^7.0.1", "got": "^11.1.4", "qs": "^6.9.4" }, diff --git a/src/client.ts b/src/client.ts index b1c06ea..b7e0c56 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5,6 +5,7 @@ * --- */ import got, { Got } from "got"; +import CacheableRequest from "cacheable-request"; export interface ClientOptions { /** @@ -47,6 +48,14 @@ export interface HttpOptions { * so when connecting to a local Moodle instance. */ rejectInvalidSSL?: boolean; + + /** + * An object that implements the Map API (such as a `new Map()` or a Keyv + * instance) can be supplied here to cache requests. This caching behavior + * is compliant with RFC 7234, and uses the `If-None-Match`/`If-Modified-Since` + * HTTP headers to revalidate stale cache entries. + */ + cache?: string | false | CacheableRequest.StorageAdapter; } /** @@ -91,6 +100,10 @@ export abstract class Client { ? httpOptions.rejectInvalidSSL : true, }, + cache: + httpOptions && httpOptions.cache !== undefined + ? httpOptions.cache + : undefined, }); } } diff --git a/tests/joodle.test.ts b/tests/joodle.test.ts index df90fa5..10a3e29 100644 --- a/tests/joodle.test.ts +++ b/tests/joodle.test.ts @@ -7,6 +7,7 @@ describe("The Joodle client class", () => { const timeout = 5000; const retries = 5; const rejectInvalidSSL = false; + const cache = new Map(); let joodle: Joodle; beforeAll(() => { @@ -19,6 +20,7 @@ describe("The Joodle client class", () => { timeout, retries, rejectInvalidSSL, + cache, } ); @@ -88,6 +90,7 @@ describe("The Joodle client class", () => { expect(joodle.got.defaults.options.https!.rejectUnauthorized).toBe( rejectInvalidSSL ); + expect(joodle.got.defaults.options.cache).toBeInstanceOf(Map); }); it("should throw an error if the baseURL is not provided", () => {