Optional prom-client peer dependency
This commit is contained in:
parent
ee67d81e94
commit
460ded7641
12
package.json
12
package.json
@ -22,7 +22,9 @@
|
|||||||
"build/**/*"
|
"build/**/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && tsc -p tsconfig.cjs.json && node ./scripts/declareTypes.js",
|
"build": "tsc && tsc -p tsconfig.cjs.json && node ./scripts/declareTypes.js --common --module",
|
||||||
|
"build:cjs": "tsc -p tsconfig.cjs.json && node ./scripts/declareTypes.js --common",
|
||||||
|
"build:module": "tsc && node ./scripts/declareTypes.js --module",
|
||||||
"test": "yarn build && jest",
|
"test": "yarn build && jest",
|
||||||
"release": "yarn build && yarn publish",
|
"release": "yarn build && yarn publish",
|
||||||
"lint": "eslint --fix"
|
"lint": "eslint --fix"
|
||||||
@ -41,5 +43,13 @@
|
|||||||
"amqplib": "^0.10.3",
|
"amqplib": "^0.10.3",
|
||||||
"mongodb": "^5.7.0",
|
"mongodb": "^5.7.0",
|
||||||
"mysql": "^2.18.1"
|
"mysql": "^2.18.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prom-client": "^15.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prom-client": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
fs.writeFileSync('./build/cjs/package.json', JSON.stringify({ type: 'commonjs' }));
|
if (process.argv.includes('--common'))
|
||||||
fs.writeFileSync('./build/esm/package.json', JSON.stringify({ type: 'module' }));
|
fs.writeFileSync('./build/cjs/package.json', JSON.stringify({ type: 'commonjs' }));
|
||||||
|
if (process.argv.includes('--module'))
|
||||||
|
fs.writeFileSync('./build/esm/package.json', JSON.stringify({ type: 'module' }));
|
@ -25,7 +25,8 @@ export type MariaOptions = {
|
|||||||
client?: PoolConfig,
|
client?: PoolConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
loggerOptions?: LoggerClientOptions,
|
loggerOptions?: LoggerClientOptions,
|
||||||
donorQuery?: boolean
|
donorQuery?: boolean,
|
||||||
|
recordMetrics?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type MariaError = {
|
type MariaError = {
|
||||||
@ -95,6 +96,8 @@ class MariaDB
|
|||||||
#pool: PoolCluster | null;
|
#pool: PoolCluster | null;
|
||||||
#nodes: Node[];
|
#nodes: Node[];
|
||||||
#canQueryDonor: boolean;
|
#canQueryDonor: boolean;
|
||||||
|
#queryHistogram?: {startTimer: (labels: object) => () => void};
|
||||||
|
#server: IServer;
|
||||||
|
|
||||||
constructor (server: IServer, options: MariaOptions)
|
constructor (server: IServer, options: MariaOptions)
|
||||||
{
|
{
|
||||||
@ -128,7 +131,7 @@ class MariaDB
|
|||||||
this.#cluster = this.#credentials.length > 1;
|
this.#cluster = this.#credentials.length > 1;
|
||||||
this.#canQueryDonor = options.donorQuery ?? false;
|
this.#canQueryDonor = options.donorQuery ?? false;
|
||||||
this.#logger = server.createLogger(this, options.loggerOptions);
|
this.#logger = server.createLogger(this, options.loggerOptions);
|
||||||
|
this.#server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
get ready ()
|
get ready ()
|
||||||
@ -141,6 +144,7 @@ class MariaDB
|
|||||||
return this.#_activeQueries;
|
return this.#_activeQueries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-lines-per-function
|
||||||
async init ()
|
async init ()
|
||||||
{
|
{
|
||||||
if (!this.#load)
|
if (!this.#load)
|
||||||
@ -230,10 +234,27 @@ class MariaDB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
const Prometheus = await import('prom-client').catch(() => null);
|
||||||
|
if (this.#config.recordMetrics && !Prometheus)
|
||||||
|
{
|
||||||
|
this.#logger.warn('Metrics recording was enabled but missing prom-client dependency');
|
||||||
|
}
|
||||||
|
else if (this.#config.recordMetrics && Prometheus)
|
||||||
|
{
|
||||||
|
this.#logger.info('Setting up metric recording');
|
||||||
|
this.#queryHistogram = new Prometheus.Histogram({
|
||||||
|
name: 'sql_queries',
|
||||||
|
help: 'Tracks query duration and frequency',
|
||||||
|
buckets: Prometheus?.exponentialBuckets(0.005, 2, 10),
|
||||||
|
labelNames: [ 'type' ] as const
|
||||||
|
});
|
||||||
|
this.#server.registerMetric(this.#queryHistogram!);
|
||||||
|
}
|
||||||
|
|
||||||
this.#_ready = true;
|
this.#_ready = true;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close ()
|
async close ()
|
||||||
@ -382,10 +403,11 @@ class MariaDB
|
|||||||
Promise<T[] | FieldInfo[] | [OkPacket]>
|
Promise<T[] | FieldInfo[] | [OkPacket]>
|
||||||
{
|
{
|
||||||
const connection = await this.getConnection(node ?? null, errorIfNodeUnavailable ?? false);
|
const connection = await this.getConnection(node ?? null, errorIfNodeUnavailable ?? false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const result = await new Promise<T[] | FieldInfo[] | [OkPacket]| undefined>((resolve, reject) =>
|
const result = await new Promise<T[] | FieldInfo[] | [OkPacket]| undefined>((resolve, reject) =>
|
||||||
{
|
{
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: [ 'SELECT', 'UPDATE', 'INSERT', 'DELETE' ].find(entry => query.toUpperCase().includes(entry)) ?? 'OTHER' });
|
||||||
const q = connection.query({ timeout, sql: query }, values, (err, results, fields) =>
|
const q = connection.query({ timeout, sql: query }, values, (err, results, fields) =>
|
||||||
{
|
{
|
||||||
if (err)
|
if (err)
|
||||||
@ -397,6 +419,8 @@ class MariaDB
|
|||||||
else
|
else
|
||||||
resolve(fields);
|
resolve(fields);
|
||||||
connection.release();
|
connection.release();
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
});
|
});
|
||||||
this.#logger.debug(`Constructed query: ${q.sql.substring(0, 1024)}`);
|
this.#logger.debug(`Constructed query: ${q.sql.substring(0, 1024)}`);
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,8 @@ export type MongoOptions = {
|
|||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
loggerOptions?: LoggerClientOptions,
|
loggerOptions?: LoggerClientOptions,
|
||||||
client?: MongoClientOptions,
|
client?: MongoClientOptions,
|
||||||
load?: boolean
|
load?: boolean,
|
||||||
|
recordMetrics?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type StringIndexable = {[key: string]: boolean | string | number | Document | object}
|
type StringIndexable = {[key: string]: boolean | string | number | Document | object}
|
||||||
@ -54,6 +55,8 @@ class MongoDB
|
|||||||
|
|
||||||
#db: Db | null;
|
#db: Db | null;
|
||||||
#_client: MongoClient;
|
#_client: MongoClient;
|
||||||
|
#queryHistogram?: { startTimer: (labels: object) => () => void };
|
||||||
|
#server: IServer;
|
||||||
|
|
||||||
constructor (server: IServer, config: MongoOptions)
|
constructor (server: IServer, config: MongoOptions)
|
||||||
{
|
{
|
||||||
@ -73,6 +76,7 @@ class MongoDB
|
|||||||
this.#load = config.load ?? true;
|
this.#load = config.load ?? true;
|
||||||
|
|
||||||
this.#logger = server.createLogger(this, config.loggerOptions);
|
this.#logger = server.createLogger(this, config.loggerOptions);
|
||||||
|
this.#server = server;
|
||||||
|
|
||||||
if (URI)
|
if (URI)
|
||||||
{
|
{
|
||||||
@ -122,7 +126,6 @@ class MongoDB
|
|||||||
*/
|
*/
|
||||||
async init ()
|
async init ()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!this.#load)
|
if (!this.#load)
|
||||||
return this.#logger.info('Not loading MongoDB');
|
return this.#logger.info('Not loading MongoDB');
|
||||||
|
|
||||||
@ -133,12 +136,29 @@ class MongoDB
|
|||||||
|
|
||||||
await this.#_client.connect();
|
await this.#_client.connect();
|
||||||
this.#logger.debug('Connected, selecting DB');
|
this.#logger.debug('Connected, selecting DB');
|
||||||
this.#db = await this.#_client.db(this.#_database);
|
this.#db = this.#_client.db(this.#_database);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
const Prometheus = await import('prom-client').catch(() => null);
|
||||||
|
if (this.#config.recordMetrics && !Prometheus)
|
||||||
|
{
|
||||||
|
this.#logger.warn('Metrics recording was enabled but missing prom-client dependency');
|
||||||
|
}
|
||||||
|
else if (this.#config.recordMetrics && Prometheus)
|
||||||
|
{
|
||||||
|
this.#logger.info('Setting up metric recording');
|
||||||
|
this.#queryHistogram = new Prometheus.Histogram({
|
||||||
|
name: 'mongo_queries',
|
||||||
|
help: 'Tracks query duration and frequency',
|
||||||
|
buckets: Prometheus?.exponentialBuckets(0.005, 2, 10),
|
||||||
|
labelNames: [ 'type' ] as const
|
||||||
|
});
|
||||||
|
this.#server.registerMetric(this.#queryHistogram!);
|
||||||
|
}
|
||||||
|
|
||||||
this.#logger.status('MongoDB ready');
|
this.#logger.status('MongoDB ready');
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close ()
|
async close ()
|
||||||
@ -167,7 +187,6 @@ class MongoDB
|
|||||||
*/
|
*/
|
||||||
async find<T extends Document> (db: string, query: MongoQuery, options?: FindOptions<T>): Promise<WithId<T>[]>
|
async find<T extends Document> (db: string, query: MongoQuery, options?: FindOptions<T>): Promise<WithId<T>[]>
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!this.#db)
|
if (!this.#db)
|
||||||
throw new Error('MongoDB not connected');
|
throw new Error('MongoDB not connected');
|
||||||
|
|
||||||
@ -179,9 +198,12 @@ class MongoDB
|
|||||||
|
|
||||||
this.#logger.debug(`Incoming find query for ${db} with parameters ${inspect(query)}`);
|
this.#logger.debug(`Incoming find query for ${db} with parameters ${inspect(query)}`);
|
||||||
|
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'find' });
|
||||||
const cursor = this.#db.collection<T>(db).find(query as Filter<T>, options);
|
const cursor = this.#db.collection<T>(db).find(query as Filter<T>, options);
|
||||||
return cursor.toArray();
|
const data = await cursor.toArray();
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,7 +216,6 @@ class MongoDB
|
|||||||
*/
|
*/
|
||||||
async findOne<T extends Document> (db: string, query: MongoQuery, options: FindOptions<T> = {}): Promise<WithId<T> | null>
|
async findOne<T extends Document> (db: string, query: MongoQuery, options: FindOptions<T> = {}): Promise<WithId<T> | null>
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!this.#db)
|
if (!this.#db)
|
||||||
throw new Error('MongoDB not connected');
|
throw new Error('MongoDB not connected');
|
||||||
if (typeof db !== 'string')
|
if (typeof db !== 'string')
|
||||||
@ -204,9 +225,11 @@ class MongoDB
|
|||||||
query._id = new ObjectId(query._id);
|
query._id = new ObjectId(query._id);
|
||||||
|
|
||||||
this.#logger.debug(`Incoming findOne query for ${db} with parameters ${inspect(query)}`);
|
this.#logger.debug(`Incoming findOne query for ${db} with parameters ${inspect(query)}`);
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'findOne' });
|
||||||
const result = await this.#db.collection<T>(db).findOne(query as Filter<T>, options);
|
const result = await this.#db.collection<T>(db).findOne(query as Filter<T>, options);
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,7 +243,6 @@ class MongoDB
|
|||||||
*/
|
*/
|
||||||
async updateMany<T extends Document> (db: string, filter: MongoQuery, data: T, upsert = false)
|
async updateMany<T extends Document> (db: string, filter: MongoQuery, data: T, upsert = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!this.#db)
|
if (!this.#db)
|
||||||
throw new Error('MongoDB not connected');
|
throw new Error('MongoDB not connected');
|
||||||
if (typeof db !== 'string')
|
if (typeof db !== 'string')
|
||||||
@ -231,9 +253,11 @@ class MongoDB
|
|||||||
filter._id = new ObjectId(filter._id);
|
filter._id = new ObjectId(filter._id);
|
||||||
|
|
||||||
this.#logger.debug(`Incoming update query for '${db}' with parameters\n${inspect(filter)}\nand data\n${inspect(data)}`);
|
this.#logger.debug(`Incoming update query for '${db}' with parameters\n${inspect(filter)}\nand data\n${inspect(data)}`);
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'updateMany' });
|
||||||
const result = await this.#db.collection<T>(db).updateMany(filter as Filter<T>, { $set: data }, { upsert });
|
const result = await this.#db.collection<T>(db).updateMany(filter as Filter<T>, { $set: data }, { upsert });
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -247,7 +271,6 @@ class MongoDB
|
|||||||
*/
|
*/
|
||||||
async updateOne (db: string, filter: MongoQuery, data: Document, upsert = false)
|
async updateOne (db: string, filter: MongoQuery, data: Document, upsert = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!this.#db)
|
if (!this.#db)
|
||||||
throw new Error('MongoDB not connected');
|
throw new Error('MongoDB not connected');
|
||||||
if (typeof db !== 'string')
|
if (typeof db !== 'string')
|
||||||
@ -256,9 +279,11 @@ class MongoDB
|
|||||||
filter._id = new ObjectId(filter._id);
|
filter._id = new ObjectId(filter._id);
|
||||||
|
|
||||||
this.#logger.debug(`Incoming updateOne query for ${db} with parameters ${inspect(filter)}`);
|
this.#logger.debug(`Incoming updateOne query for ${db} with parameters ${inspect(filter)}`);
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'updateOne' });
|
||||||
const result = await this.#db.collection(db).updateOne(filter as Filter<Document>, { $set: data }, { upsert });
|
const result = await this.#db.collection(db).updateOne(filter as Filter<Document>, { $set: data }, { upsert });
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,7 +297,6 @@ class MongoDB
|
|||||||
*/
|
*/
|
||||||
async insertOne (db: string, data: Document)
|
async insertOne (db: string, data: Document)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!this.#db)
|
if (!this.#db)
|
||||||
throw new Error('MongoDB not connected');
|
throw new Error('MongoDB not connected');
|
||||||
if (typeof db !== 'string')
|
if (typeof db !== 'string')
|
||||||
@ -281,9 +305,11 @@ class MongoDB
|
|||||||
data._id = new ObjectId(data._id);
|
data._id = new ObjectId(data._id);
|
||||||
|
|
||||||
this.#logger.debug(`Incoming insertOne query for ${db} with parameters ${inspect(data)}`);
|
this.#logger.debug(`Incoming insertOne query for ${db} with parameters ${inspect(data)}`);
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'insertOne' });
|
||||||
const result = await this.#db.collection(db).insertOne(data);
|
const result = await this.#db.collection(db).insertOne(data);
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOne (db: string, filter: Document)
|
async deleteOne (db: string, filter: Document)
|
||||||
@ -296,7 +322,10 @@ class MongoDB
|
|||||||
filter._id = new ObjectId(filter._id);
|
filter._id = new ObjectId(filter._id);
|
||||||
|
|
||||||
this.#logger.debug(`Incoming deleteOne query for ${db} with parameters ${inspect(filter)}`);
|
this.#logger.debug(`Incoming deleteOne query for ${db} with parameters ${inspect(filter)}`);
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'deleteOne' });
|
||||||
const result = await this.#db.collection(db).deleteOne(filter);
|
const result = await this.#db.collection(db).deleteOne(filter);
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,7 +340,10 @@ class MongoDB
|
|||||||
if (typeof filter._id === 'string')
|
if (typeof filter._id === 'string')
|
||||||
filter._id = new ObjectId(filter._id);
|
filter._id = new ObjectId(filter._id);
|
||||||
|
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'findOneAndDelete' });
|
||||||
const result = await this.#db.collection<T>(db).findOneAndDelete(filter as Filter<T>, { includeResultMetadata: meta });
|
const result = await this.#db.collection<T>(db).findOneAndDelete(filter as Filter<T>, { includeResultMetadata: meta });
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +367,10 @@ class MongoDB
|
|||||||
filter._id = new ObjectId(filter._id);
|
filter._id = new ObjectId(filter._id);
|
||||||
|
|
||||||
this.#logger.debug(`Incoming push query for ${db}, with upsert ${upsert} and with parameters ${inspect(filter)} and data ${inspect(data)}`);
|
this.#logger.debug(`Incoming push query for ${db}, with upsert ${upsert} and with parameters ${inspect(filter)} and data ${inspect(data)}`);
|
||||||
|
const endTimer = this.#queryHistogram?.startTimer({ type: 'push' });
|
||||||
const result = await this.#db.collection(db).updateOne(filter, { $push: data }, { upsert });
|
const result = await this.#db.collection(db).updateOne(filter, { $push: data }, { upsert });
|
||||||
|
if (endTimer)
|
||||||
|
endTimer();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +385,6 @@ class MongoDB
|
|||||||
*/
|
*/
|
||||||
random<T extends Document> (db: string, filter: Document = {}, amount = 1)
|
random<T extends Document> (db: string, filter: Document = {}, amount = 1)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!this.#db)
|
if (!this.#db)
|
||||||
throw new Error('MongoDB not connected');
|
throw new Error('MongoDB not connected');
|
||||||
if (typeof db !== 'string')
|
if (typeof db !== 'string')
|
||||||
@ -365,7 +399,6 @@ class MongoDB
|
|||||||
|
|
||||||
const cursor = this.#db.collection(db).aggregate<T>([{ $match: filter }, { $sample: { size: amount } }]);
|
const cursor = this.#db.collection(db).aggregate<T>([{ $match: filter }, { $sample: { size: amount } }]);
|
||||||
return cursor.toArray();
|
return cursor.toArray();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stats (options = {})
|
stats (options = {})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ILogger, LoggerClientOptions } from "./Logger.js";
|
import { ILogger, LoggerClientOptions } from './Logger.js';
|
||||||
|
|
||||||
export interface IServer {
|
export interface IServer {
|
||||||
createLogger(obj: object, options?: Partial<LoggerClientOptions>): ILogger
|
createLogger(obj: object, options?: Partial<LoggerClientOptions>): ILogger
|
||||||
|
registerMetric(metric: object): void
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user