|
|
@@ -47,9 +47,83 @@ export type CachedSession = {
|
|
|
* @description
|
|
|
* This strategy defines how sessions get cached. Since most requests will need the Session
|
|
|
* object for permissions data, it can become a bottleneck to go to the database and do a multi-join
|
|
|
- * SQL query each time. Therefore we cache the session data only perform the SQL query once and upon
|
|
|
+ * SQL query each time. Therefore, we cache the session data only perform the SQL query once and upon
|
|
|
* invalidation of the cache.
|
|
|
*
|
|
|
+ * The Vendure default is to use a the {@link InMemorySessionCacheStrategy}, which is fast and suitable for
|
|
|
+ * single-instance deployments. However, for multi-instance deployments (horizontally scaled, serverless etc.),
|
|
|
+ * you will need to define a custom strategy that stores the session cache in a shared data store, such as in the
|
|
|
+ * DB or in Redis.
|
|
|
+ *
|
|
|
+ * Here's an example implementation using Redis. To use this, you need to add the
|
|
|
+ * [ioredis package](https://www.npmjs.com/package/ioredis) as a dependency.
|
|
|
+ *
|
|
|
+ * @example
|
|
|
+ * ```TypeScript
|
|
|
+ * import { CachedSession, Logger, SessionCacheStrategy, VendurePlugin } from '\@vendure/core';
|
|
|
+ * import IORedis from 'ioredis';
|
|
|
+ *
|
|
|
+ * export interface RedisSessionCachePluginOptions {
|
|
|
+ * namespace?: string;
|
|
|
+ * redisOptions?: IORedis.RedisOptions;
|
|
|
+ * }
|
|
|
+ * const loggerCtx = 'RedisSessionCacheStrategy';
|
|
|
+ * const DEFAULT_NAMESPACE = 'vendure-session-cache';
|
|
|
+ *
|
|
|
+ * export class RedisSessionCacheStrategy implements SessionCacheStrategy {
|
|
|
+ * private client: IORedis.Redis;
|
|
|
+ * constructor(private options: RedisSessionCachePluginOptions) {}
|
|
|
+ *
|
|
|
+ * init() {
|
|
|
+ * this.client = new IORedis(this.options.redisOptions);
|
|
|
+ * this.client.on('error', err => Logger.error(err.message, loggerCtx, err.stack));
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * async get(sessionToken: string): Promise<CachedSession | undefined> {
|
|
|
+ * const retrieved = await this.client.get(this.namespace(sessionToken));
|
|
|
+ * if (retrieved) {
|
|
|
+ * try {
|
|
|
+ * return JSON.parse(retrieved);
|
|
|
+ * } catch (e) {
|
|
|
+ * Logger.error(`Could not parse cached session data: ${e.message}`, loggerCtx);
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * async set(session: CachedSession) {
|
|
|
+ * await this.client.set(this.namespace(session.token), JSON.stringify(session));
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * async delete(sessionToken: string) {
|
|
|
+ * await this.client.del(this.namespace(sessionToken));
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * clear() {
|
|
|
+ * // not implemented
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * private namespace(key: string) {
|
|
|
+ * return `${this.options.namespace ?? DEFAULT_NAMESPACE}:${key}`;
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * \@VendurePlugin({
|
|
|
+ * configuration: config => {
|
|
|
+ * config.authOptions.sessionCacheStrategy = new RedisSessionCacheStrategy(
|
|
|
+ * RedisSessionCachePlugin.options,
|
|
|
+ * );
|
|
|
+ * return config;
|
|
|
+ * },
|
|
|
+ * })
|
|
|
+ * export class RedisSessionCachePlugin {
|
|
|
+ * static options: RedisSessionCachePluginOptions;
|
|
|
+ * static init(options: RedisSessionCachePluginOptions) {
|
|
|
+ * this.options = options;
|
|
|
+ * return this;
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * ```
|
|
|
+ *
|
|
|
* @docsCategory auth
|
|
|
* @docsPage SessionCacheStrategy
|
|
|
* @docsWeight 0
|