From 535c19ad8618de95f05acb5f21f6b3cd4bf89f48 Mon Sep 17 00:00:00 2001 From: Hailey Date: Sun, 7 Sep 2025 12:56:04 -0700 Subject: [PATCH 1/7] add prom metrics to blink --- bskylink/package.json | 1 + bskylink/src/bin.ts | 3 + bskylink/src/cache/safelinkClient.ts | 34 ++++++++++- bskylink/src/config.ts | 4 ++ bskylink/src/context.ts | 3 + bskylink/src/index.ts | 24 +++++++- bskylink/src/metrics.ts | 79 ++++++++++++++++++++++++++ bskylink/src/routes/createShortLink.ts | 12 ++++ bskylink/src/routes/redirect.ts | 10 ++++ bskylink/src/routes/shortLink.ts | 11 +++- bskylink/yarn.lock | 25 ++++++++ 11 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 bskylink/src/metrics.ts diff --git a/bskylink/package.json b/bskylink/package.json index c57cb3cd89..0d9b8444d3 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -21,6 +21,7 @@ "lru-cache": "^11.1.0", "pg": "^8.12.0", "pino": "^9.2.0", + "prom-client": "^15.1.3", "uint8arrays": "^5.1.0" }, "devDependencies": { diff --git a/bskylink/src/bin.ts b/bskylink/src/bin.ts index 3e0746a989..f5deaa40b7 100644 --- a/bskylink/src/bin.ts +++ b/bskylink/src/bin.ts @@ -24,6 +24,9 @@ async function main() { await link.destroy() httpLogger.info('link service is stopped') }) + + await link.ctx.metrics.start(cfg.service.metricsPort) + httpLogger.info('link metrics is running') } main() diff --git a/bskylink/src/cache/safelinkClient.ts b/bskylink/src/cache/safelinkClient.ts index 547a9c8438..720642db32 100644 --- a/bskylink/src/cache/safelinkClient.ts +++ b/bskylink/src/cache/safelinkClient.ts @@ -12,6 +12,7 @@ import {type ServiceConfig} from '../config.js' import type Database from '../db/index.js' import {type SafelinkRule} from '../db/schema.js' import {redirectLogger} from '../logger.js' +import {type Metrics} from '../metrics.js' const SAFELINK_MIN_FETCH_INTERVAL = 1_000 const SAFELINK_MAX_FETCH_INTERVAL = 10_000 @@ -22,12 +23,21 @@ export class SafelinkClient { private urlCache: LRUCache private db: Database + private metrics: Metrics private ozoneAgent: OzoneAgent private cursor?: string - constructor({cfg, db}: {cfg: ServiceConfig; db: Database}) { + constructor({ + cfg, + db, + metrics, + }: { + cfg: ServiceConfig + db: Database + metrics: Metrics + }) { this.domainCache = new LRUCache({ max: 10000, }) @@ -37,6 +47,7 @@ export class SafelinkClient { }) this.db = db + this.metrics = metrics this.ozoneAgent = new OzoneAgent( cfg.safelinkPdsUrl!, @@ -46,6 +57,17 @@ export class SafelinkClient { } public async tryFindRule(link: string): Promise { + const start = process.hrtime.bigint() + const addMetrics = (status: 'ok' | 'error', cached: boolean) => { + const end = process.hrtime.bigint() + const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3 + + this.metrics.safeLinkLookups.labels(status, cached ? 'yes' : 'no').inc() + this.metrics.safeLinkLookupDuration + .labels(status, cached ? 'yes' : 'no') + .observe(respTimeMs) + } + let url: string let domain: string try { @@ -56,6 +78,7 @@ export class SafelinkClient { {error: e, inputUrl: link}, 'failed to normalize looked up link', ) + addMetrics('error', false) // fail open return 'ok' } @@ -65,18 +88,23 @@ export class SafelinkClient { // _and_ it is not 'ok'. const urlRule = this.urlCache.get(url) if (urlRule && urlRule !== 'ok') { + addMetrics('ok', true) return urlRule } // If we find a domain rule of _any_ kind, including 'ok', we can now return that rule. const domainRule = this.domainCache.get(domain) if (domainRule) { + addMetrics('ok', true) return domainRule } try { const maybeUrlRule = await this.getRule(this.db, url, 'url') this.urlCache.set(url, maybeUrlRule) + + addMetrics('ok', false) + return maybeUrlRule } catch (e) { this.urlCache.set(url, 'ok') @@ -85,11 +113,15 @@ export class SafelinkClient { try { const maybeDomainRule = await this.getRule(this.db, domain, 'domain') this.domainCache.set(domain, maybeDomainRule) + + addMetrics('ok', false) + return maybeDomainRule } catch (e) { this.domainCache.set(domain, 'ok') } + addMetrics('ok', false) return 'ok' } diff --git a/bskylink/src/config.ts b/bskylink/src/config.ts index 795a7210ff..b45fd38356 100644 --- a/bskylink/src/config.ts +++ b/bskylink/src/config.ts @@ -7,6 +7,7 @@ export type Config = { export type ServiceConfig = { port: number + metricsPort: number version?: string hostnames: string[] hostnamesSet: Set @@ -32,6 +33,7 @@ export type DbPoolConfig = { export type Environment = { port?: number + metricsPort?: number version?: string hostnames: string[] appHostname?: string @@ -50,6 +52,7 @@ export type Environment = { export const readEnv = (): Environment => { return { port: envInt('LINK_PORT'), + metricsPort: envInt('LINK_METRICS_PORT'), version: envStr('LINK_VERSION'), hostnames: envList('LINK_HOSTNAMES'), appHostname: envStr('LINK_APP_HOSTNAME'), @@ -71,6 +74,7 @@ export const readEnv = (): Environment => { export const envToCfg = (env: Environment): Config => { const serviceCfg: ServiceConfig = { port: env.port ?? 3000, + metricsPort: env.metricsPort ?? 9090, version: env.version, hostnames: env.hostnames, hostnamesSet: new Set(env.hostnames), diff --git a/bskylink/src/context.ts b/bskylink/src/context.ts index 1520513ceb..adc7fb1e10 100644 --- a/bskylink/src/context.ts +++ b/bskylink/src/context.ts @@ -1,6 +1,7 @@ import {SafelinkClient} from './cache/safelinkClient.js' import {type Config} from './config.js' import Database from './db/index.js' +import {Metrics} from './metrics.js' export type AppContextOptions = { cfg: Config @@ -10,6 +11,7 @@ export type AppContextOptions = { export class AppContext { cfg: Config db: Database + metrics: Metrics = new Metrics() safelinkClient: SafelinkClient abortController = new AbortController() @@ -19,6 +21,7 @@ export class AppContext { this.safelinkClient = new SafelinkClient({ cfg: this.opts.cfg.service, db: this.opts.db, + metrics: this.metrics, }) } diff --git a/bskylink/src/index.ts b/bskylink/src/index.ts index c7d52681df..51462fec9e 100644 --- a/bskylink/src/index.ts +++ b/bskylink/src/index.ts @@ -2,7 +2,7 @@ import events from 'node:events' import type http from 'node:http' import cors from 'cors' -import express from 'express' +import express, {type Response} from 'express' import {createHttpTerminator, type HttpTerminator} from 'http-terminator' import {type Config} from './config.js' @@ -32,6 +32,28 @@ export class LinkService { app = routes(ctx, app) app.use(errorHandler) + // request duration logging + app.use((req, res, next) => { + const start = process.hrtime.bigint() + const originalEnd = res.end.bind(res) as Response['end'] + res.end = function ( + this: Response, + ...args: Parameters + ): ReturnType { + const end = process.hrtime.bigint() + const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3 + + if (req.route) { + ctx.metrics.requestDuration + .labels(req.route.path, req.method, res.statusCode.toString()) + .observe(respTimeMs) + } + + return originalEnd(...args) + } as Response['end'] + next() + }) + return new LinkService(app, ctx) } diff --git a/bskylink/src/metrics.ts b/bskylink/src/metrics.ts new file mode 100644 index 0000000000..7ffc58364d --- /dev/null +++ b/bskylink/src/metrics.ts @@ -0,0 +1,79 @@ +import {once} from 'events' +import express, {type Application} from 'express' +import * as prometheus from 'prom-client' + +export class Metrics { + private app: Application + + private registry: prometheus.Registry + + requestDuration: prometheus.Histogram + redirects: prometheus.Counter + shortLinkRequests: prometheus.Counter + safeLinkLookups: prometheus.Counter + safeLinkLookupDuration: prometheus.Histogram + + constructor() { + const app = express() + this.app = app + + const registry = new prometheus.Registry() + this.registry = registry + + // Add add metrics route to express + app.get('/metrics', async (_req, res) => { + res.set('content-type', registry.contentType) + res.end(await registry.metrics()) + }) + + // Collect default service metrics + prometheus.collectDefaultMetrics({ + prefix: 'blink_', + register: this.registry, + gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], + }) + + // Add other metrics + this.requestDuration = new prometheus.Histogram({ + name: 'blink_request_duration_millis', + help: 'Request duration in mmillis', + labelNames: ['path', 'method', 'code'], + buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + registers: [registry], + }) + + this.redirects = new prometheus.Counter({ + name: 'blink_redirects', + help: 'Number of link redirects handled', + labelNames: ['safelink_rule', 'code'], + registers: [registry], + }) + + this.shortLinkRequests = new prometheus.Counter({ + name: 'blink_shortlink_requests', + help: 'Number of shortlink requests handled', + labelNames: ['method', 'code'], + registers: [registry], + }) + + this.safeLinkLookups = new prometheus.Counter({ + name: 'blink_safelink_lookups', + help: 'Number of safelink lookups handled', + labelNames: ['status', 'cached'], + registers: [registry], + }) + + this.safeLinkLookupDuration = new prometheus.Histogram({ + name: 'blink_safelink_lookup_duration_millis', + help: 'Request duration in millis', + labelNames: ['status', 'cached'], + buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + registers: [registry], + }) + } + + async start(port: number) { + const server = this.app.listen(port) + await once(server, 'listening') + } +} diff --git a/bskylink/src/routes/createShortLink.ts b/bskylink/src/routes/createShortLink.ts index 629119059f..2eeb5960e5 100644 --- a/bskylink/src/routes/createShortLink.ts +++ b/bskylink/src/routes/createShortLink.ts @@ -13,16 +13,24 @@ export default function (ctx: AppContext, app: Express) { '/link', bodyParser.json(), handler(async (req, res) => { + const addMetrics = (statusCode: number) => { + ctx.metrics.shortLinkRequests + .labels('POST', statusCode.toString()) + .inc() + } + let path: string if (typeof req.body?.path === 'string') { path = req.body.path } else { + addMetrics(400) return res.status(400).json({ error: 'InvalidPath', message: '"path" parameter is missing or not a string', }) } if (!path.startsWith('/')) { + addMetrics(400) return res.status(400).json({ error: 'InvalidPath', message: @@ -34,6 +42,7 @@ export default function (ctx: AppContext, app: Express) { // link pattern: /start/{did}/{rkey} if (!parts[1].startsWith('did:')) { // enforce strong links + addMetrics(400) return res.status(400).json({ error: 'InvalidPath', message: @@ -41,8 +50,11 @@ export default function (ctx: AppContext, app: Express) { }) } const id = await ensureLink(ctx, LinkType.StarterPack, parts) + addMetrics(200) return res.json({url: getUrl(ctx, req, id)}) } + + addMetrics(400) return res.status(400).json({ error: 'InvalidPath', message: '"path" parameter does not have a known format', diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index 681dc0bb90..d4ce26ff70 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -19,6 +19,10 @@ export default function (ctx: AppContext, app: Express) { return app.get( '/redirect', handler(async (req, res) => { + const addMetrics = (ruleStr: string, statusCode: number) => { + ctx.metrics.redirects.labels(ruleStr, statusCode.toString()).inc() + } + let link = req.query.u assert( typeof link === 'string', @@ -39,6 +43,7 @@ export default function (ctx: AppContext, app: Express) { ) { res.setHeader('Cache-Control', 'no-store') res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`) + addMetrics('bad_url', 302) return res.status(302).end() } @@ -49,9 +54,12 @@ export default function (ctx: AppContext, app: Express) { let html: string | undefined + let ruleStr = 'ok' if (ctx.cfg.service.safelinkEnabled) { const rule = await ctx.safelinkClient.tryFindRule(link) if (rule !== 'ok') { + ruleStr = rule.action + switch (rule.action) { case 'whitelist': redirectLogger.info({rule}, 'Whitelist rule matched') @@ -89,6 +97,8 @@ export default function (ctx: AppContext, app: Express) { html = linkRedirectContents(url.href) } + addMetrics(ruleStr, 302) + return res.end(html) }), ) diff --git a/bskylink/src/routes/shortLink.ts b/bskylink/src/routes/shortLink.ts index 276aae1ca1..c46d1e2c67 100644 --- a/bskylink/src/routes/shortLink.ts +++ b/bskylink/src/routes/shortLink.ts @@ -1,15 +1,19 @@ import assert from 'node:assert' import {DAY, SECOND} from '@atproto/common' -import {Express} from 'express' +import {type Express} from 'express' -import {AppContext} from '../context.js' +import {type AppContext} from '../context.js' import {handler} from './util.js' export default function (ctx: AppContext, app: Express) { return app.get( '/:linkId', handler(async (req, res) => { + const addMetrics = (statusCode: number) => { + ctx.metrics.shortLinkRequests.labels('GET', statusCode.toString()).inc() + } + const linkId = req.params.linkId const contentType = req.accepts(['html', 'json']) assert( @@ -35,6 +39,7 @@ export default function (ctx: AppContext, app: Express) { } // send the user to the app res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`) + addMetrics(302) return res.status(302).end() } // build url from original url in order to preserve query params @@ -45,9 +50,11 @@ export default function (ctx: AppContext, app: Express) { url.pathname = found.path res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`) if (contentType === 'json') { + addMetrics(302) return res.json({url: url.href}).end() } res.setHeader('Location', url.href) + addMetrics(302) return res.status(301).end() }), ) diff --git a/bskylink/yarn.lock b/bskylink/yarn.lock index f30f67cb1c..9676e82b2d 100644 --- a/bskylink/yarn.lock +++ b/bskylink/yarn.lock @@ -161,6 +161,11 @@ dependencies: make-plural "^7.0.0" +"@opentelemetry/api@^1.4.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + "@tsconfig/node10@^1.0.7": version "1.0.11" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" @@ -338,6 +343,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bintrees@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" + integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== + body-parser@1.20.2, body-parser@^1.20.2: version "1.20.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" @@ -1060,6 +1070,14 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== +prom-client@^15.1.3: + version "15.1.3" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2" + integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g== + dependencies: + "@opentelemetry/api" "^1.4.0" + tdigest "^0.1.1" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -1232,6 +1250,13 @@ string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +tdigest@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" + integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== + dependencies: + bintrees "1.0.2" + thread-stream@^2.6.0: version "2.7.0" resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" From 450611ab2ca8caa5b158388be38e79bca867f0ef Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Sep 2025 00:23:00 -0700 Subject: [PATCH 2/7] cleanup --- bskylink/src/cache/safelinkClient.ts | 2 -- bskylink/src/metrics.ts | 2 +- bskylink/src/routes/redirect.ts | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bskylink/src/cache/safelinkClient.ts b/bskylink/src/cache/safelinkClient.ts index 720642db32..a3baa13d77 100644 --- a/bskylink/src/cache/safelinkClient.ts +++ b/bskylink/src/cache/safelinkClient.ts @@ -104,7 +104,6 @@ export class SafelinkClient { this.urlCache.set(url, maybeUrlRule) addMetrics('ok', false) - return maybeUrlRule } catch (e) { this.urlCache.set(url, 'ok') @@ -115,7 +114,6 @@ export class SafelinkClient { this.domainCache.set(domain, maybeDomainRule) addMetrics('ok', false) - return maybeDomainRule } catch (e) { this.domainCache.set(domain, 'ok') diff --git a/bskylink/src/metrics.ts b/bskylink/src/metrics.ts index 7ffc58364d..2310f1359f 100644 --- a/bskylink/src/metrics.ts +++ b/bskylink/src/metrics.ts @@ -74,6 +74,6 @@ export class Metrics { async start(port: number) { const server = this.app.listen(port) - await once(server, 'listening') + await once(server, 'metrics server listening') } } diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index d4ce26ff70..43de79d530 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -98,7 +98,6 @@ export default function (ctx: AppContext, app: Express) { } addMetrics(ruleStr, 302) - return res.end(html) }), ) From 416bbbde74a1b2b6844ca3e81f023698af5c4ad9 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 19 Sep 2025 14:25:43 -0700 Subject: [PATCH 3/7] use ts-util package --- bskylink/package.json | 1 - bskylink/src/cache/safelinkClient.ts | 44 ++++++++----------- bskylink/src/context.ts | 60 +++++++++++++++++++++++--- bskylink/src/index.ts | 3 +- bskylink/src/routes/createShortLink.ts | 3 +- bskylink/src/routes/redirect.ts | 5 ++- bskylink/src/routes/shortLink.ts | 5 ++- 7 files changed, 83 insertions(+), 38 deletions(-) diff --git a/bskylink/package.json b/bskylink/package.json index 0d9b8444d3..c57cb3cd89 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -21,7 +21,6 @@ "lru-cache": "^11.1.0", "pg": "^8.12.0", "pino": "^9.2.0", - "prom-client": "^15.1.3", "uint8arrays": "^5.1.0" }, "devDependencies": { diff --git a/bskylink/src/cache/safelinkClient.ts b/bskylink/src/cache/safelinkClient.ts index a3baa13d77..ed0e30bbe3 100644 --- a/bskylink/src/cache/safelinkClient.ts +++ b/bskylink/src/cache/safelinkClient.ts @@ -8,11 +8,10 @@ import {ExpiredTokenError} from '@atproto/api/dist/client/types/com/atproto/serv import {MINUTE} from '@atproto/common' import {LRUCache} from 'lru-cache' -import {type ServiceConfig} from '../config.js' +import {type AppContext} from '../context.js' import type Database from '../db/index.js' import {type SafelinkRule} from '../db/schema.js' import {redirectLogger} from '../logger.js' -import {type Metrics} from '../metrics.js' const SAFELINK_MIN_FETCH_INTERVAL = 1_000 const SAFELINK_MAX_FETCH_INTERVAL = 10_000 @@ -22,22 +21,12 @@ export class SafelinkClient { private domainCache: LRUCache private urlCache: LRUCache - private db: Database - private metrics: Metrics + private ctx: AppContext private ozoneAgent: OzoneAgent - private cursor?: string - constructor({ - cfg, - db, - metrics, - }: { - cfg: ServiceConfig - db: Database - metrics: Metrics - }) { + constructor(ctx: AppContext) { this.domainCache = new LRUCache({ max: 10000, }) @@ -46,13 +35,12 @@ export class SafelinkClient { max: 25000, }) - this.db = db - this.metrics = metrics + this.ctx = ctx this.ozoneAgent = new OzoneAgent( - cfg.safelinkPdsUrl!, - cfg.safelinkAgentIdentifier!, - cfg.safelinkAgentPass!, + this.ctx.cfg.service.safelinkPdsUrl!, + this.ctx.cfg.service.safelinkAgentIdentifier!, + this.ctx.cfg.service.safelinkAgentPass!, ) } @@ -62,8 +50,12 @@ export class SafelinkClient { const end = process.hrtime.bigint() const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3 - this.metrics.safeLinkLookups.labels(status, cached ? 'yes' : 'no').inc() - this.metrics.safeLinkLookupDuration + this.ctx.metrics + .getCounter('safeLinkLookups') + .labels(status, cached ? 'yes' : 'no') + .inc() + this.ctx.metrics + .getHistogram('safeLinkLookupDuration') .labels(status, cached ? 'yes' : 'no') .observe(respTimeMs) } @@ -100,7 +92,7 @@ export class SafelinkClient { } try { - const maybeUrlRule = await this.getRule(this.db, url, 'url') + const maybeUrlRule = await this.getRule(this.ctx.db, url, 'url') this.urlCache.set(url, maybeUrlRule) addMetrics('ok', false) @@ -110,7 +102,7 @@ export class SafelinkClient { } try { - const maybeDomainRule = await this.getRule(this.db, domain, 'domain') + const maybeDomainRule = await this.getRule(this.ctx.db, domain, 'domain') this.domainCache.set(domain, maybeDomainRule) addMetrics('ok', false) @@ -249,7 +241,7 @@ export class SafelinkClient { redirectLogger.info('received no new safelink events from ozone') setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL) } else { - await this.db.transaction(async db => { + await this.ctx.db.transaction(async db => { for (const rule of res.data.events) { switch (rule.eventType) { case 'removeRule': @@ -277,7 +269,7 @@ export class SafelinkClient { private async getCursor() { if (this.cursor === '') { - const res = await this.db.db + const res = await this.ctx.db.db .selectFrom('safelink_cursor') .selectAll() .where('id', '=', 1) @@ -293,7 +285,7 @@ export class SafelinkClient { private async setCursor(cursor: string) { const updatedAt = new Date() try { - await this.db.db + await this.ctx.db.db .insertInto('safelink_cursor') .values({ id: 1, diff --git a/bskylink/src/context.ts b/bskylink/src/context.ts index adc7fb1e10..a057647e65 100644 --- a/bskylink/src/context.ts +++ b/bskylink/src/context.ts @@ -1,28 +1,74 @@ +import {type MetricConfig, Metrics} from '@haileyok/ts-util' + import {SafelinkClient} from './cache/safelinkClient.js' import {type Config} from './config.js' import Database from './db/index.js' -import {Metrics} from './metrics.js' export type AppContextOptions = { cfg: Config db: Database } +type BlinkMetricNames = + | 'requestDuration' + | 'redirects' + | 'shortLinkRequests' + | 'safeLinkLookups' + | 'safeLinkLookupDuration' + +type BlinkMetricConfig = Record + export class AppContext { cfg: Config db: Database - metrics: Metrics = new Metrics() + metrics: Metrics safelinkClient: SafelinkClient abortController = new AbortController() constructor(private opts: AppContextOptions) { + const metricsConfig: BlinkMetricConfig = { + requestDuration: { + type: 'histogram', + name: 'request_duration_millis', + help: 'Request duration in millis', + labelNames: ['path', 'method', 'code'], + buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + }, + redirects: { + type: 'counter', + name: 'redirects', + help: 'Number of link redirects handled', + labelNames: ['safelink_rule', 'code'], + }, + shortLinkRequests: { + type: 'counter', + name: 'shortlink_requests', + help: 'Number of shortlink requests handled', + labelNames: ['method', 'code'], + }, + safeLinkLookups: { + type: 'counter', + name: 'safelink_lookups', + help: 'Number of safelink lookups handled', + labelNames: ['status', 'cached'], + }, + safeLinkLookupDuration: { + type: 'histogram', + name: 'safelink_lookup_duration_millis', + help: 'Request duration in millis', + labelNames: ['status', 'cached'], + buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + }, + } + + this.metrics = new Metrics(metricsConfig, { + prefix: 'blink_', + collectDefaultMetrics: true, + }) + this.cfg = this.opts.cfg this.db = this.opts.db - this.safelinkClient = new SafelinkClient({ - cfg: this.opts.cfg.service, - db: this.opts.db, - metrics: this.metrics, - }) + this.safelinkClient = new SafelinkClient(this) } static async fromConfig(cfg: Config, overrides?: Partial) { diff --git a/bskylink/src/index.ts b/bskylink/src/index.ts index 51462fec9e..b9a58e1709 100644 --- a/bskylink/src/index.ts +++ b/bskylink/src/index.ts @@ -44,7 +44,8 @@ export class LinkService { const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3 if (req.route) { - ctx.metrics.requestDuration + ctx.metrics + .getHistogram('requestDuration') .labels(req.route.path, req.method, res.statusCode.toString()) .observe(respTimeMs) } diff --git a/bskylink/src/routes/createShortLink.ts b/bskylink/src/routes/createShortLink.ts index 2eeb5960e5..ad8037f7ba 100644 --- a/bskylink/src/routes/createShortLink.ts +++ b/bskylink/src/routes/createShortLink.ts @@ -14,7 +14,8 @@ export default function (ctx: AppContext, app: Express) { bodyParser.json(), handler(async (req, res) => { const addMetrics = (statusCode: number) => { - ctx.metrics.shortLinkRequests + ctx.metrics + .getCounter('shortLinkRequests') .labels('POST', statusCode.toString()) .inc() } diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index 43de79d530..a32aea7614 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -20,7 +20,10 @@ export default function (ctx: AppContext, app: Express) { '/redirect', handler(async (req, res) => { const addMetrics = (ruleStr: string, statusCode: number) => { - ctx.metrics.redirects.labels(ruleStr, statusCode.toString()).inc() + ctx.metrics + .getCounter('redirects') + .labels(ruleStr, statusCode.toString()) + .inc() } let link = req.query.u diff --git a/bskylink/src/routes/shortLink.ts b/bskylink/src/routes/shortLink.ts index c46d1e2c67..08ffe40bba 100644 --- a/bskylink/src/routes/shortLink.ts +++ b/bskylink/src/routes/shortLink.ts @@ -11,7 +11,10 @@ export default function (ctx: AppContext, app: Express) { '/:linkId', handler(async (req, res) => { const addMetrics = (statusCode: number) => { - ctx.metrics.shortLinkRequests.labels('GET', statusCode.toString()).inc() + ctx.metrics + .getCounter('shortLinkRequests') + .labels('GET', statusCode.toString()) + .inc() } const linkId = req.params.linkId From 9a0007026bdd6d536da5cadf9dc3ab47a3aebd35 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 19 Sep 2025 14:27:01 -0700 Subject: [PATCH 4/7] add ts-util package --- bskylink/package.json | 1 + bskylink/yarn.lock | 631 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 619 insertions(+), 13 deletions(-) diff --git a/bskylink/package.json b/bskylink/package.json index c57cb3cd89..9e3cf0110b 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -10,6 +10,7 @@ "dependencies": { "@atproto/api": "^0.16.7", "@atproto/common": "^0.4.11", + "@haileyok/ts-util": "^1.0.0", "@types/escape-html": "^1.0.4", "body-parser": "^1.20.2", "cors": "^2.8.5", diff --git a/bskylink/yarn.lock b/bskylink/yarn.lock index 9676e82b2d..7efc624a0f 100644 --- a/bskylink/yarn.lock +++ b/bskylink/yarn.lock @@ -62,6 +62,20 @@ "@atproto/lexicon" "^0.5.0" zod "^3.23.8" +"@babel/code-frame@^7.0.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + "@cbor-extract/cbor-extract-darwin-arm64@2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz#8d65cb861a99622e1b4a268e2d522d2ec6137338" @@ -99,6 +113,18 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@haileyok/ts-util@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@haileyok/ts-util/-/ts-util-1.0.0.tgz#30ea08c8cfdcd673d821a35a670285e83c2ac4b1" + integrity sha512-tszfjVXXFJt93ii7COtlJzGsjkI6trNgXd3hbhvTvs9muIbbd4F7zkB3RzNVARytyK4emlMa7hj7jscBIGogSA== + dependencies: + cors "^2.8.5" + events "^3.3.0" + express "^5.1.0" + prom-client "^15.1.3" + tslint "^6.1.3" + typescript "^5.9.2" + "@ipld/dag-cbor@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e" @@ -298,6 +324,14 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -318,11 +352,25 @@ acorn@^8.11.0, acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -338,6 +386,11 @@ await-lock@^2.2.2: resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -366,11 +419,34 @@ body-parser@1.20.2, body-parser@^1.20.2: type-is "~1.6.18" unpipe "1.0.0" +body-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" + integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.0" + http-errors "^2.0.0" + iconv-lite "^0.6.3" + on-finished "^2.4.1" + qs "^6.14.0" + raw-body "^3.0.0" + type-is "^2.0.0" + boolean@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -379,11 +455,24 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -bytes@3.1.2: +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ== + +bytes@3.1.2, bytes@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -395,6 +484,14 @@ call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + cbor-extract@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.2.0.tgz#cee78e630cbeae3918d1e2e58e0cebaf3a3be840" @@ -421,6 +518,37 @@ cborg@^1.6.0: resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.2.tgz#83cd581b55b3574c816f82696307c7512db759a1" integrity sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug== +chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +commander@^2.12.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -428,7 +556,14 @@ content-disposition@0.5.4: dependencies: safe-buffer "5.2.1" -content-type@~1.0.4, content-type@~1.0.5: +content-disposition@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" + integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -438,11 +573,21 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + cookie@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + cors@^2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" @@ -470,6 +615,13 @@ debug@^4.3.3: dependencies: ms "^2.1.3" +debug@^4.3.5, debug@^4.4.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -484,7 +636,7 @@ delay@^5.0.0: resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== -depd@2.0.0: +depd@2.0.0, depd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -504,11 +656,25 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -521,17 +687,39 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -etag@~1.8.1: +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +etag@^1.8.1, etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== @@ -583,6 +771,39 @@ express@^4.19.2: utils-merge "1.0.1" vary "~1.1.2" +express@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" + integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.0" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + fast-printf@^1.6.9: version "1.6.9" resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.9.tgz#212f56570d2dc8ccdd057ee93d50dd414d07d676" @@ -608,6 +829,18 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +finalhandler@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" + integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -618,6 +851,16 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -634,6 +877,42 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +glob@^7.1.1: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -641,11 +920,21 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graphemer@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + has-property-descriptors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" @@ -663,14 +952,19 @@ has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -hasown@^2.0.0: +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" -http-errors@2.0.0: +http-errors@2.0.0, http-errors@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== @@ -710,12 +1004,34 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.0.tgz#c50cd80e6746ca8115eb98743afa81aa0e147a3e" + integrity sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -inherits@2.0.4: +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -725,11 +1041,36 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + iso-datestring-validator@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + kysely@^0.27.3: version "0.27.3" resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276" @@ -755,16 +1096,31 @@ math-interval-parser@^2.0.1: resolved "https://registry.yarnpkg.com/math-interval-parser/-/math-interval-parser-2.0.1.tgz#e22cd6d15a0a7f4c03aec560db76513da615bed4" integrity sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -775,6 +1131,18 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" + integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + dependencies: + mime-db "^1.54.0" + mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" @@ -787,6 +1155,25 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^0.5.3: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + moo@^0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c" @@ -822,6 +1209,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + node-gyp-build-optional-packages@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz#52b143b9dd77b7669073cbfe39e3f4118bfc603c" @@ -839,6 +1231,11 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + obuf@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -849,13 +1246,20 @@ on-exit-leak-free@^2.1.0: resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== -on-finished@2.4.1: +on-finished@2.4.1, on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -875,16 +1279,31 @@ p-wait-for@^3.2.0: dependencies: p-timeout "^3.0.0" -parseurl@~1.3.3: +parseurl@^1.3.3, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== + pg-cloudflare@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" @@ -959,6 +1378,11 @@ pgpass@1.x: dependencies: split2 "^4.1.0" +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + pino-abstract-transport@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" @@ -1078,7 +1502,7 @@ prom-client@^15.1.3: "@opentelemetry/api" "^1.4.0" tdigest "^0.1.1" -proxy-addr@~2.0.7: +proxy-addr@^2.0.7, proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -1093,12 +1517,19 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + quick-format-unescaped@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== -range-parser@~1.2.1: +range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== @@ -1113,6 +1544,16 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.1.tgz#ced5cd79a77bbb0496d707f2a0f9e1ae3aecdcb1" + integrity sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.7.0" + unpipe "1.0.0" + readable-stream@^4.0.0: version "4.5.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" @@ -1129,6 +1570,15 @@ real-require@^0.2.0: resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== +resolve@^1.3.2: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + roarr@^7.0.4: version "7.21.1" resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.21.1.tgz#fd6452ca822a65f736c35e5372f04ee9f2ca3851" @@ -1138,6 +1588,17 @@ roarr@^7.0.4: safe-stable-stringify "^2.4.3" semver-compare "^1.0.0" +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + safe-buffer@5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -1153,7 +1614,7 @@ safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.3: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -1163,6 +1624,11 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== +semver@^5.3.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -1182,6 +1648,23 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +send@^1.1.0, send@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" + integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== + dependencies: + debug "^4.3.5" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.0" + mime-types "^3.0.1" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" + serve-static@1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" @@ -1192,6 +1675,16 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +serve-static@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" + integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -1209,6 +1702,35 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -1219,6 +1741,17 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + sonic-boom@^3.7.0: version "3.8.1" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.1.tgz#d5ba8c4e26d6176c9a1d14d549d9ff579a163422" @@ -1238,11 +1771,21 @@ split2@^4.0.0, split2@^4.1.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +statuses@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1250,6 +1793,18 @@ string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + tdigest@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" @@ -1300,11 +1855,51 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tslib@^1.13.0, tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslint@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.3" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.13.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + type-fest@^2.3.3: version "2.19.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== +type-is@^2.0.0, type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -1318,6 +1913,11 @@ typescript@^5.4.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + uint8arrays@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" @@ -1352,11 +1952,16 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -vary@^1, vary@~1.1.2: +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From bec44fc983200aedc20fa28f1b017ba135ce6d8a Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 23 Sep 2025 10:00:01 -0700 Subject: [PATCH 5/7] rm old metrics.ts --- bskylink/src/metrics.ts | 79 ----------------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 bskylink/src/metrics.ts diff --git a/bskylink/src/metrics.ts b/bskylink/src/metrics.ts deleted file mode 100644 index 2310f1359f..0000000000 --- a/bskylink/src/metrics.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {once} from 'events' -import express, {type Application} from 'express' -import * as prometheus from 'prom-client' - -export class Metrics { - private app: Application - - private registry: prometheus.Registry - - requestDuration: prometheus.Histogram - redirects: prometheus.Counter - shortLinkRequests: prometheus.Counter - safeLinkLookups: prometheus.Counter - safeLinkLookupDuration: prometheus.Histogram - - constructor() { - const app = express() - this.app = app - - const registry = new prometheus.Registry() - this.registry = registry - - // Add add metrics route to express - app.get('/metrics', async (_req, res) => { - res.set('content-type', registry.contentType) - res.end(await registry.metrics()) - }) - - // Collect default service metrics - prometheus.collectDefaultMetrics({ - prefix: 'blink_', - register: this.registry, - gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], - }) - - // Add other metrics - this.requestDuration = new prometheus.Histogram({ - name: 'blink_request_duration_millis', - help: 'Request duration in mmillis', - labelNames: ['path', 'method', 'code'], - buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], - registers: [registry], - }) - - this.redirects = new prometheus.Counter({ - name: 'blink_redirects', - help: 'Number of link redirects handled', - labelNames: ['safelink_rule', 'code'], - registers: [registry], - }) - - this.shortLinkRequests = new prometheus.Counter({ - name: 'blink_shortlink_requests', - help: 'Number of shortlink requests handled', - labelNames: ['method', 'code'], - registers: [registry], - }) - - this.safeLinkLookups = new prometheus.Counter({ - name: 'blink_safelink_lookups', - help: 'Number of safelink lookups handled', - labelNames: ['status', 'cached'], - registers: [registry], - }) - - this.safeLinkLookupDuration = new prometheus.Histogram({ - name: 'blink_safelink_lookup_duration_millis', - help: 'Request duration in millis', - labelNames: ['status', 'cached'], - buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], - registers: [registry], - }) - } - - async start(port: number) { - const server = this.app.listen(port) - await once(server, 'metrics server listening') - } -} From cd6feb8d59f1aad186ac7b51a51332b479ecbbfc Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 23 Sep 2025 10:22:59 -0700 Subject: [PATCH 6/7] update ts util lib with new express metrics registration --- bskylink/package.json | 2 +- bskylink/src/index.ts | 25 ++-------------------- bskylink/yarn.lock | 50 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/bskylink/package.json b/bskylink/package.json index 9e3cf0110b..f0a3978b89 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -10,7 +10,7 @@ "dependencies": { "@atproto/api": "^0.16.7", "@atproto/common": "^0.4.11", - "@haileyok/ts-util": "^1.0.0", + "@haileyok/ts-util": "^1.3.3", "@types/escape-html": "^1.0.4", "body-parser": "^1.20.2", "cors": "^2.8.5", diff --git a/bskylink/src/index.ts b/bskylink/src/index.ts index b9a58e1709..bc8f3253eb 100644 --- a/bskylink/src/index.ts +++ b/bskylink/src/index.ts @@ -2,7 +2,7 @@ import events from 'node:events' import type http from 'node:http' import cors from 'cors' -import express, {type Response} from 'express' +import express from 'express' import {createHttpTerminator, type HttpTerminator} from 'http-terminator' import {type Config} from './config.js' @@ -32,28 +32,7 @@ export class LinkService { app = routes(ctx, app) app.use(errorHandler) - // request duration logging - app.use((req, res, next) => { - const start = process.hrtime.bigint() - const originalEnd = res.end.bind(res) as Response['end'] - res.end = function ( - this: Response, - ...args: Parameters - ): ReturnType { - const end = process.hrtime.bigint() - const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3 - - if (req.route) { - ctx.metrics - .getHistogram('requestDuration') - .labels(req.route.path, req.method, res.statusCode.toString()) - .observe(respTimeMs) - } - - return originalEnd(...args) - } as Response['end'] - next() - }) + ctx.metrics.registerExpressMetrics(app) return new LinkService(app, ctx) } diff --git a/bskylink/yarn.lock b/bskylink/yarn.lock index 7efc624a0f..5ee2bc2748 100644 --- a/bskylink/yarn.lock +++ b/bskylink/yarn.lock @@ -113,14 +113,16 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@haileyok/ts-util@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@haileyok/ts-util/-/ts-util-1.0.0.tgz#30ea08c8cfdcd673d821a35a670285e83c2ac4b1" - integrity sha512-tszfjVXXFJt93ii7COtlJzGsjkI6trNgXd3hbhvTvs9muIbbd4F7zkB3RzNVARytyK4emlMa7hj7jscBIGogSA== +"@haileyok/ts-util@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@haileyok/ts-util/-/ts-util-1.3.3.tgz#2bc5005c58ef9251765676f9707e1671bd712d33" + integrity sha512-1Djml2jn7hG8pniQfN3TaZQJ+IijIdNzlcEFhB3bBBoGz7/vjHNDfWMwbGLCpsP0vdGrbhjE7YIZy0U1CGLUXw== dependencies: cors "^2.8.5" events "^3.3.0" express "^5.1.0" + express-prom-bundle "^8.0.0" + kafkajs "^2.2.4" prom-client "^15.1.3" tslint "^6.1.3" typescript "^5.9.2" @@ -249,6 +251,16 @@ "@types/range-parser" "*" "@types/send" "*" +"@types/express-serve-static-core@^5.0.0": + version "5.0.7" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6" + integrity sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + "@types/express@^4.17.21": version "4.17.23" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" @@ -259,6 +271,15 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/express@^5.0.0": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" + integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "*" + "@types/http-errors@*": version "2.0.5" resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" @@ -734,6 +755,15 @@ events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +express-prom-bundle@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/express-prom-bundle/-/express-prom-bundle-8.0.0.tgz#22a434b87854d98a1ea75c487c9d65f669e8e93e" + integrity sha512-UHdpaMks6Z/tvxQsNzhsE7nkdXb4/zEh/jwN0tfZSZOEF+aD0dlfl085EU4jveOq09v01c5sIUfjV4kJODZ2eQ== + dependencies: + "@types/express" "^5.0.0" + on-finished "^2.3.0" + url-value-parser "^2.0.0" + express@^4.19.2: version "4.19.2" resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" @@ -1071,6 +1101,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +kafkajs@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-2.2.4.tgz#59e6e16459d87fdf8b64be73970ed5aa42370a5b" + integrity sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA== + kysely@^0.27.3: version "0.27.3" resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276" @@ -1246,7 +1281,7 @@ on-exit-leak-free@^2.1.0: resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== -on-finished@2.4.1, on-finished@^2.4.1: +on-finished@2.4.1, on-finished@^2.3.0, on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -1942,6 +1977,11 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +url-value-parser@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/url-value-parser/-/url-value-parser-2.2.0.tgz#f38ae8cd24604ec69bc219d66929ddbbd93a2b32" + integrity sha512-yIQdxJpgkPamPPAPuGdS7Q548rLhny42tg8d4vyTNzFqvOnwqrgHXvgehT09U7fwrzxi3RxCiXjoNUNnNOlQ8A== + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" From 308daf2c8a73ec47160ae36b5abcf762b18fdfff Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 23 Sep 2025 10:28:18 -0700 Subject: [PATCH 7/7] bump --- bskylink/package.json | 2 +- bskylink/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bskylink/package.json b/bskylink/package.json index f0a3978b89..4289be0158 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -10,7 +10,7 @@ "dependencies": { "@atproto/api": "^0.16.7", "@atproto/common": "^0.4.11", - "@haileyok/ts-util": "^1.3.3", + "@haileyok/ts-util": "^1.3.4", "@types/escape-html": "^1.0.4", "body-parser": "^1.20.2", "cors": "^2.8.5", diff --git a/bskylink/yarn.lock b/bskylink/yarn.lock index 5ee2bc2748..9e71916479 100644 --- a/bskylink/yarn.lock +++ b/bskylink/yarn.lock @@ -113,10 +113,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@haileyok/ts-util@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@haileyok/ts-util/-/ts-util-1.3.3.tgz#2bc5005c58ef9251765676f9707e1671bd712d33" - integrity sha512-1Djml2jn7hG8pniQfN3TaZQJ+IijIdNzlcEFhB3bBBoGz7/vjHNDfWMwbGLCpsP0vdGrbhjE7YIZy0U1CGLUXw== +"@haileyok/ts-util@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@haileyok/ts-util/-/ts-util-1.3.4.tgz#6f76b8f03cd2e33e9965a7a2f42b9b82504cb5ec" + integrity sha512-WWjthZgZ6WVfmJoDVJM7BrBeH4aUrQcWajaeQqi8lWAZeWklDVXFtKei53f1CZEiaKm2P0tGjxW5zmQ4+vi/4g== dependencies: cors "^2.8.5" events "^3.3.0"