UNPKG

@backstage/backend-defaults

Version:

Backend defaults used by Backstage backend apps

181 lines (177 loc) • 6.04 kB
'use strict'; var errors = require('@backstage/errors'); var mysql = require('./connectors/mysql.cjs.js'); var postgres = require('./connectors/postgres.cjs.js'); var sqlite3 = require('./connectors/sqlite3.cjs.js'); function pluginPath(pluginId) { return `plugin.${pluginId}`; } class DatabaseManagerImpl { config; connectors; options; databaseCache; keepaliveIntervals; constructor(config, connectors, options, databaseCache = /* @__PURE__ */ new Map(), keepaliveIntervals = /* @__PURE__ */ new Map()) { this.config = config; this.connectors = connectors; this.options = options; this.databaseCache = databaseCache; this.keepaliveIntervals = keepaliveIntervals; if (options?.rootLifecycle !== void 0) { options.rootLifecycle.addShutdownHook(async () => { await this.shutdown({ logger: options.rootLogger }); }); } } /** * Generates a DatabaseService for consumption by plugins. * * @param pluginId - The plugin that the database manager should be created for. Plugin names * should be unique as they are used to look up database config overrides under * `backend.database.plugin`. */ forPlugin(pluginId, deps) { const client = this.getClientType(pluginId).client; const connector = this.connectors[client]; if (!connector) { throw new Error( `Unsupported database client type '${client}' specified for plugin '${pluginId}'` ); } const getClient = () => this.getDatabase(pluginId, connector, deps); const skip = this.options?.migrations?.skip ?? this.config.getOptionalBoolean(`plugin.${pluginId}.skipMigrations`) ?? this.config.getOptionalBoolean("skipMigrations") ?? false; return { getClient, migrations: { skip } }; } /** * Destroys all known connections. */ async shutdown(deps) { const pluginIds = Array.from(this.databaseCache.keys()); await Promise.allSettled( pluginIds.map(async (pluginId) => { clearInterval(this.keepaliveIntervals.get(pluginId)); const connection = await this.databaseCache.get(pluginId); if (connection) { if (connection.client.config.includes("sqlite3")) { return; } await connection.destroy().catch((error) => { deps?.logger?.error( `Problem closing database connection for ${pluginId}: ${errors.stringifyError( error )}` ); }); } }) ); } /** * Provides the client type which should be used for a given plugin. * * The client type is determined by plugin specific config if present. * Otherwise the base client is used as the fallback. * * @param pluginId - Plugin to get the client type for * @returns Object with client type returned as `client` and boolean * representing whether or not the client was overridden as * `overridden` */ getClientType(pluginId) { const pluginClient = this.config.getOptionalString( `${pluginPath(pluginId)}.client` ); const baseClient = this.config.getString("client"); const client = pluginClient ?? baseClient; return { client, overridden: client !== baseClient }; } /** * Provides a scoped Knex client for a plugin as per application config. * * @param pluginId - Plugin to get a Knex client for * @returns Promise which resolves to a scoped Knex database client for a * plugin */ async getDatabase(pluginId, connector, deps) { if (this.databaseCache.has(pluginId)) { return this.databaseCache.get(pluginId); } const clientPromise = connector.getClient(pluginId, deps); this.databaseCache.set(pluginId, clientPromise); if (process.env.NODE_ENV !== "test") { clientPromise.then( (client) => this.startKeepaliveLoop(pluginId, client, deps.logger) ); } return clientPromise; } startKeepaliveLoop(pluginId, client, logger) { let lastKeepaliveFailed = false; this.keepaliveIntervals.set( pluginId, setInterval(() => { client?.raw("select 1").then( () => { lastKeepaliveFailed = false; }, (error) => { if (!lastKeepaliveFailed) { lastKeepaliveFailed = true; logger.warn( `Database keepalive failed for plugin ${pluginId}, ${errors.stringifyError( error )}` ); } } ); }, 60 * 1e3) ); } } class DatabaseManager { /** * Creates a {@link DatabaseManager} from `backend.database` config. * * @param config - The loaded application configuration. * @param options - An optional configuration object. */ static fromConfig(config, options) { const databaseConfig = config.getConfig("backend.database"); const prefix = databaseConfig.getOptionalString("prefix") || "backstage_plugin_"; return new DatabaseManager( new DatabaseManagerImpl( databaseConfig, { pg: new postgres.PgConnector(databaseConfig, prefix), sqlite3: new sqlite3.Sqlite3Connector(databaseConfig), "better-sqlite3": new sqlite3.Sqlite3Connector(databaseConfig), mysql: new mysql.MysqlConnector(databaseConfig, prefix), mysql2: new mysql.MysqlConnector(databaseConfig, prefix) }, options ) ); } impl; constructor(impl) { this.impl = impl; } /** * Generates a DatabaseService for consumption by plugins. * * @param pluginId - The plugin that the database manager should be created for. Plugin names * should be unique as they are used to look up database config overrides under * `backend.database.plugin`. */ forPlugin(pluginId, deps) { return this.impl.forPlugin(pluginId, deps); } } exports.DatabaseManager = DatabaseManager; exports.DatabaseManagerImpl = DatabaseManagerImpl; //# sourceMappingURL=DatabaseManager.cjs.js.map