@backstage/backend-defaults
Version:
Backend defaults used by Backstage backend apps
181 lines (177 loc) • 6.04 kB
JavaScript
;
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