UNPKG

@backstage/backend-defaults

Version:

Backend defaults used by Backstage backend apps

346 lines (340 loc) • 12.1 kB
'use strict'; var Keyv = require('keyv'); var CacheClient = require('./CacheClient.cjs.js'); var types$1 = require('./types.cjs.js'); var InfinispanOptionsMapper = require('./providers/infinispan/InfinispanOptionsMapper.cjs.js'); var types = require('@backstage/types'); var config = require('@backstage/config'); var InfinispanKeyvStore = require('./providers/infinispan/InfinispanKeyvStore.cjs.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var Keyv__default = /*#__PURE__*/_interopDefaultCompat(Keyv); class CacheManager { /** * Keys represent supported `backend.cache.store` values, mapped to factories * that return Keyv instances appropriate to the store. */ storeFactories = { redis: this.createRedisStoreFactory(), valkey: this.createValkeyStoreFactory(), memcache: this.createMemcacheStoreFactory(), memory: this.createMemoryStoreFactory(), infinispan: this.createInfinispanStoreFactory() }; logger; store; connection; errorHandler; defaultTtl; storeOptions; /** * Creates a new {@link CacheManager} instance by reading from the `backend` * config section, specifically the `.cache` key. * * @param config - The loaded application configuration. * @param options - Optional configuration for the CacheManager. * @returns A new CacheManager instance. */ static fromConfig(config$1, options = {}) { const store = config$1.getOptionalString("backend.cache.store") || "memory"; const defaultTtlConfig = config$1.getOptional("backend.cache.defaultTtl"); const connectionString = config$1.getOptionalString("backend.cache.connection") || ""; const logger = options.logger?.child({ type: "cacheManager" }); if (config$1.has("backend.cache.useRedisSets")) { logger?.warn( "The 'backend.cache.useRedisSets' configuration key is deprecated and no longer has any effect. The underlying '@keyv/redis' and '@keyv/valkey' libraries no longer support redis sets." ); } let defaultTtl; if (defaultTtlConfig !== void 0) { if (typeof defaultTtlConfig === "number") { defaultTtl = defaultTtlConfig; } else { defaultTtl = types.durationToMilliseconds( config.readDurationFromConfig(config$1, { key: "backend.cache.defaultTtl" }) ); } } const storeOptions = CacheManager.parseStoreOptions(store, config$1, logger); return new CacheManager( store, connectionString, options.onError, logger, defaultTtl, storeOptions ); } /** * Parse store-specific options from configuration. * * @param store - The cache store type ('redis', 'valkey', 'memcache', 'infinispan', or 'memory') * @param config - The configuration service * @param logger - Optional logger for warnings * @returns The parsed store options */ static parseStoreOptions(store, config, logger) { const storeConfigPath = `backend.cache.${store}`; if (!config.has(storeConfigPath)) { logger?.warn( `No configuration found for cache store '${store}' at '${storeConfigPath}'.` ); } if (store === "redis" || store === "valkey") { return CacheManager.parseRedisOptions( store, storeConfigPath, config, logger ); } if (store === "infinispan") { return InfinispanOptionsMapper.InfinispanOptionsMapper.parseInfinispanOptions( storeConfigPath, config, logger ); } return void 0; } /** * Parse Redis-specific options from configuration. */ static parseRedisOptions(store, storeConfigPath, config$1, logger) { const redisOptions = { type: store }; const redisConfig = config$1.getOptionalConfig(storeConfigPath) ?? new config.ConfigReader({}); redisOptions.client = { namespace: redisConfig.getOptionalString("client.namespace"), keyPrefixSeparator: redisConfig.getOptionalString("client.keyPrefixSeparator") || ":", clearBatchSize: redisConfig.getOptionalNumber("client.clearBatchSize"), useUnlink: redisConfig.getOptionalBoolean("client.useUnlink"), noNamespaceAffectsAll: redisConfig.getOptionalBoolean( "client.noNamespaceAffectsAll" ) }; if (redisConfig.has("cluster")) { const clusterConfig = redisConfig.getConfig("cluster"); if (!clusterConfig.has("rootNodes")) { logger?.warn( `Redis cluster config has no 'rootNodes' key, defaulting to non-clustered mode` ); return redisOptions; } redisOptions.cluster = { rootNodes: clusterConfig.get("rootNodes"), defaults: clusterConfig.getOptional("defaults"), minimizeConnections: clusterConfig.getOptionalBoolean( "minimizeConnections" ), useReplicas: clusterConfig.getOptionalBoolean("useReplicas"), maxCommandRedirections: clusterConfig.getOptionalNumber( "maxCommandRedirections" ) }; } return redisOptions; } /** @internal */ constructor(store, connectionString, errorHandler, logger, defaultTtl, storeOptions) { if (!this.storeFactories.hasOwnProperty(store)) { throw new Error(`Unknown cache store: ${store}`); } this.logger = logger; this.store = store; this.connection = connectionString; this.errorHandler = errorHandler; this.defaultTtl = defaultTtl; this.storeOptions = storeOptions; } /** * Generates a PluginCacheManager for consumption by plugins. * * @param pluginId - The plugin that the cache manager should be created for. * Plugin names should be unique. */ forPlugin(pluginId) { const clientFactory = (options) => { const ttl = options.defaultTtl ?? this.defaultTtl; return this.getClientWithTtl( pluginId, ttl !== void 0 ? types$1.ttlToMilliseconds(ttl) : void 0 ); }; return new CacheClient.DefaultCacheClient(clientFactory({}), clientFactory, {}); } getClientWithTtl(pluginId, ttl) { return this.storeFactories[this.store](pluginId, ttl); } createRedisStoreFactory() { const KeyvRedis = require("@keyv/redis").default; const { createCluster } = require("@keyv/redis"); const stores = {}; return (pluginId, defaultTtl) => { if (this.storeOptions?.type !== "redis") { throw new Error( `Internal error: Wrong config type passed to redis factory: ${this.storeOptions?.type}` ); } if (!stores[pluginId]) { const redisOptions = this.storeOptions?.client || { keyPrefixSeparator: ":" }; if (this.storeOptions?.cluster) { const cluster = createCluster(this.storeOptions?.cluster); stores[pluginId] = new KeyvRedis(cluster, redisOptions); } else { stores[pluginId] = new KeyvRedis(this.connection, redisOptions); } stores[pluginId].on("error", (err) => { this.logger?.error("Failed to create redis cache client", err); this.errorHandler?.(err); }); } return new Keyv__default.default({ namespace: pluginId, ttl: defaultTtl, store: stores[pluginId], emitErrors: false, useKeyPrefix: false }); }; } createValkeyStoreFactory() { const KeyvValkey = require("@keyv/valkey").default; const { createCluster } = require("@keyv/valkey"); const stores = {}; return (pluginId, defaultTtl) => { if (this.storeOptions?.type !== "valkey") { throw new Error( `Internal error: Wrong config type passed to valkey factory: ${this.storeOptions?.type}` ); } if (!stores[pluginId]) { const valkeyOptions = this.storeOptions?.client || { keyPrefixSeparator: ":" }; if (this.storeOptions?.cluster) { const cluster = createCluster(this.storeOptions?.cluster); stores[pluginId] = new KeyvValkey(cluster, valkeyOptions); } else { stores[pluginId] = new KeyvValkey(this.connection, valkeyOptions); } stores[pluginId].on("error", (err) => { this.logger?.error("Failed to create valkey cache client", err); this.errorHandler?.(err); }); } return new Keyv__default.default({ namespace: pluginId, ttl: defaultTtl, store: stores[pluginId], emitErrors: false, useKeyPrefix: false }); }; } createMemcacheStoreFactory() { const KeyvMemcache = require("@keyv/memcache").default; const stores = {}; return (pluginId, defaultTtl) => { if (!stores[pluginId]) { stores[pluginId] = new KeyvMemcache(this.connection); stores[pluginId].on("error", (err) => { this.logger?.error("Failed to create memcache cache client", err); this.errorHandler?.(err); }); } return new Keyv__default.default({ namespace: pluginId, ttl: defaultTtl, emitErrors: false, store: stores[pluginId] }); }; } createMemoryStoreFactory() { const store = /* @__PURE__ */ new Map(); return (pluginId, defaultTtl) => new Keyv__default.default({ namespace: pluginId, ttl: defaultTtl, emitErrors: false, store }); } createInfinispanStoreFactory() { const stores = {}; return (pluginId, defaultTtl) => { if (this.storeOptions?.type !== "infinispan") { throw new Error( `Internal error: Wrong config type passed to infinispan factory: ${this.storeOptions?.type}` ); } if (!stores[pluginId]) { const isTest = process.env.NODE_ENV === "test" || typeof jest !== "undefined"; const clientPromise = isTest ? this.createInfinispanClientSync() : this.createInfinispanClientAsync(); this.logger?.info( `Creating Infinispan cache client for plugin ${pluginId} isTest = ${isTest}` ); const storeInstance = new InfinispanKeyvStore.InfinispanKeyvStore({ clientPromise, logger: this.logger }); stores[pluginId] = storeInstance; storeInstance.on("error", (err) => { this.logger?.error("Failed to create infinispan cache client", err); this.errorHandler?.(err); }); } return new Keyv__default.default({ namespace: pluginId, ttl: defaultTtl, store: stores[pluginId], emitErrors: false }); }; } /** * Creates an Infinispan client using dynamic import (production use). * @returns Promise that resolves to an Infinispan client */ async createInfinispanClientAsync() { return this.createInfinispanClient(false); } /** * Creates an Infinispan client using synchronous import (testing purposes). * @returns Promise that resolves to an Infinispan client */ createInfinispanClientSync() { return this.createInfinispanClient(true); } /** * Creates an Infinispan client based on the provided configuration. * @param useSync - Whether to use synchronous import (for testing) or dynamic import * @returns Promise that resolves to an Infinispan client */ async createInfinispanClient(useSync = false) { try { this.logger?.info("Creating Infinispan client"); if (this.storeOptions?.type === "infinispan") { const infinispan = useSync ? require("infinispan") : await import('infinispan'); const client = await infinispan.client( this.storeOptions.servers, this.storeOptions.options ); this.logger?.info("Infinispan client created successfully"); return client; } throw new Error("Infinispan store options are not defined"); } catch (error) { this.logger?.error("Failed to create Infinispan client", { error: error.message }); throw error; } } } exports.CacheManager = CacheManager; //# sourceMappingURL=CacheManager.cjs.js.map