Skip to content
This repository was archived by the owner on Mar 20, 2024. It is now read-only.

Conversation

@stafyniaksacha
Copy link
Collaborator

@stafyniaksacha stafyniaksacha commented Feb 25, 2021

⚠️ This PR introduces breaking changes if you use withKoaContext or withStrapiMiddleware options
no breaking changes are present in middleware options
strapi > 3.4.0 required

New features

Associate custom routes to a model cache configuration

// config/middleware.js

module.exports = ({ env }) => ({
  // ...
  settings: {
    // ...
    cache: {
      enabled: true,
      models: [
        "review", // same as { model: "review" },
        {
          model: "restaurant",
          routes: [
            "/restaurants/:id/orders", // same as { path: "/restaurants/:id/orders", method: "GET" },
            { path: "/restaurants/orders", method: "POST" },
            { path: "/restaurants/:id/orders", method: "PUT" },
            { path: "/restaurants/:id/orders", method: "PATCH" },
            { path: "/restaurants/:id/orders", method: "DELETE" },
          ],
        },
      },
    },
  },
})
  • GET requests are cached
  • POST, PUT, PATCH and DELETE requests are used to purge related cache

By default routes are populated with the public api routes.
To disable this behaviour add injectDefaultRoutes: false to the model cache configuration

Cache is not looked up if Authorization or Cookie header are present.
To dissable this behaviour add hitpass: false to the model cache configuration

Associate a model with a plugin

// config/middleware.js

module.exports = ({ env }) => ({
  // ...
  settings: {
    // ...
    cache: {
      enabled: true,
      models: [
        {
          model: "my-plugin-model",
          plugin: "plugin-test",
          routes: [
            { path: "/plugin-test/controller/:id", method: "GET" },
            { path: "/plugin-test/controller/:slug", method: "GET" },
            { path: "/plugin-test/controller", method: "POST" },
            { path: "/plugin-test/controller/:id", method: "PUT" },
            { path: "/plugin-test/controller/:id", method: "DELETE" },
            { path: "/plugin-test/controller/:slug", method: "DELETE" },
          ],
        },
      },
    },
  },
})

Internal refactor

  • Improved configuration resolving
  • Remove lodash usage as es6 is better in memory footprint and execution time
  • Inspired by Varnish
  • Enable Typescript type checking (with JSDoc) for better maintainability and developer experience:
// config/middleware.js

/**
 * @type {import('strapi-middleware-cache').UserMiddlewareCacheConfig}
 */
const cache = {
  enabled: true,
  models: [
    'footer',
    'universal',
  ]
}

module.exports = ({env}) => ({
  settings: {
    cache,
  }
})

Options

Middleware default options

/**
 * @typedef {Object} UserMiddlewareCacheConfig
 * @property {'mem'|'redis'=} type
 * @property {boolean=} enabled
 * @property {boolean=} logs
 * @property {boolean=} populateContext
 * @property {boolean=} populateStrapiMiddleware
 * @property {boolean=} enableEtagSupport
 * @property {boolean=} enableXCacheHeaders
 * @property {boolean=} clearRelatedCache
 * @property {boolean=} withKoaContext
 * @property {boolean=} withStrapiMiddleware
 * @property {string[]=} headers
 * @property {number=} max
 * @property {number=} maxAge
 * @property {number=} cacheTimeout
 * @property {(UserModelCacheConfig | string)[]=} models
 * @property {Object=} redisConfig
 */
const defaultOptions = {
  type: "mem",
  enabled: false,
  logs: true,
  populateContext: false,
  populateStrapiMiddleware: false,
  enableEtagSupport: false,
  enableXCacheHeaders: false,
  clearRelatedCache: false,
  withKoaContext: false,
  withStrapiMiddleware: false,
  headers: [],
  max: 500,
  maxAge: 3600000,
  cacheTimeout: 500,
  models: [],
  redisConfig: undefined,
};

Model default options

/**
 * @typedef {Object} UserModelCacheConfig
 * @property {string} model
 * @property {string=} plugin
 * @property {boolean=} singleType
 * @property {Hitpass|boolean=} hitpass
 * @property {boolean=} injectDefaultRoutes
 * @property {string[]=} headers
 * @property {number=} maxAge
 * @property {(string | CustomRoute)[]=} routes
 */
const modelOptions = {
  model: '<required>',
  plugin: undefined,
  singleType: false,
  hitpass: (ctx) => ctx.request.headers.authorization || ctx.request.headers.cookie,
  injectDefaultRoutes: true,
  headers: userOptions.headers ?? defaultOptions.headers,
  maxAge: userOptions.maxAge ?? defaultOptions.maxAge,
  routes: [],
};

New API

// options.withStrapiMiddleware = true

strapi.middleware.cache = {
 /**
  * @typedef {Object} CacheStore
  * @property {function(string): any} get
  * @property {function(string): any} peek
  * @property {function(string, any, number?): boolean} set
  * @property {function(string): void} del
  * @property {function(): any[]} keys
  * @property {function(): void} reset
  */
  store,

  /**
   * @typedef {Object} MiddlewareCacheConfig
   * @property {'mem'|'redis'} type
   * @property {boolean} enabled
   * @property {boolean} logs
   * @property {boolean} populateContext
   * @property {boolean} populateStrapiMiddleware
   * @property {boolean} enableEtagSupport
   * @property {boolean} enableXCacheHeaders
   * @property {boolean} clearRelatedCache
   * @property {boolean} withKoaContext
   * @property {boolean} withStrapiMiddleware
   * @property {number} max
   * @property {number} maxAge
   * @property {number} cacheTimeout
   * @property {string[]} headers
   * @property {ModelCacheConfig[]} models
   * @property {Object=} redisConfig
   */
  options,

  /**
   * Clear cache with uri parameters
   * 
   * @param {ModelCacheConfig} cacheConf
   * @param {{ [key: string]: string; }=} params
   */
  clearCache,

  /**
   * Get related ModelCacheConfig
   *
   * @param {string} model
   * @param {string=} plugin
   * @returns {ModelCacheConfig=}
   */
  getCacheConfig,

  /**
   * Get related ModelCacheConfig with an uid
   * 
   * uid:
   * - application::sport.sport
   * - plugins::users-permissions.user
   *
   * @param {string} uid
   * @returns {ModelCacheConfig=}
   */
  getCacheConfigByUid,

  /**
   * Get models uid that is related to a ModelCacheConfig
   *
   * @param {ModelCacheConfig} cacheConf The model used to find related caches to purge
   * @return {string[]} Array of related models uid
   */
  getRelatedModelsUid,

  /**
   * Get regexs to match all ModelCacheConfig keys with given params
   * 
   * @param {ModelCacheConfig} cacheConf
   * @param {{ [key: string]: string; }=} params
   * @returns {RegExp[]}
   */
  getCacheConfRegExp,

  /**
   * Get regexs to match CustomRoute keys with given params
   *
   * @param {CustomRoute} route
   * @param {{ [key: string]: string; }=} params
   * @returns {RegExp[]}
   */
  getRouteRegExp,
};

🚧 Request for comments
Feel free to provide feedback

Todo

  • clearCache RegExp refactor
  • getRelatedModels refactor
  • improve misconfiguration warning / errors
  • check cache-control response header ?
  • find a way for hitpass requests ? (see Varnish diagram)
  • tests

@patrixr
Copy link
Owner

patrixr commented Feb 26, 2021

Hey @stafyniaksacha, I'm loving this ❤️ !

Some thoughts:

  • Could you describe the breaking changes. I'm not too bothered personally by it but withKoaContext and withStrapiMiddleware were user requested features so I know for a fact some people are using them.

  • Might be worth supporting wildcards or arrays for methods to avoid repetition e.g { path: "/restaurants/:id/orders", method: ["PUT", "POST"] },

  • Given how extensive your changes are, I created a beta branch which auto-publishes to NPM under the beta tag. That allows you to make multiple PRs to that branch and test them using npm install --save strapi-middleware-cache@2.0.1-beta.0 (you can bump the build number as you go along). You can rebase your current branch on the beta branch and merge it there (added you as a collab)

  • I'll be much more comfortable releasing this if it is tested, although the current test setup is a bit awkward, so you have my blessing if you want to set it up any other way

  • Thumbs up on lodash removal and typing 👍

FYI: I'm on the strapi slack if you need anything

@stafyniaksacha stafyniaksacha changed the base branch from master to beta February 26, 2021 05:08
@stafyniaksacha stafyniaksacha changed the title feat: enable support of custom routes and models from plugins (wip) feat: enable support of custom routes and models from plugins Feb 26, 2021
@stafyniaksacha stafyniaksacha merged commit 9af2c24 into patrixr:beta Feb 26, 2021
@gfragioud
Copy link

Any update on this?
Do you still maintain this project because there is a long time since the last commit...

@stafyniaksacha
Copy link
Collaborator Author

stafyniaksacha commented Sep 9, 2021

Hello @gfragioud !
Yes we still support the plugin (only the beta version) ! We did not merged to main branch because we did not write tests, but the plugin seem to be stable unless you need advanced authentication management. You can read more about here : #41

I plan to work on the next version during the next week to support Strapi V4

Edit: I also plan to create a real documentation for the plugin, more info coming on next week ;)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants