use ts-util package

This commit is contained in:
Hailey
2025-09-19 14:25:43 -07:00
parent 450611ab2c
commit 416bbbde74
7 changed files with 83 additions and 38 deletions
-1
View File
@@ -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": {
+18 -26
View File
@@ -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<string, SafelinkRule | 'ok'>
private urlCache: LRUCache<string, SafelinkRule | 'ok'>
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<string, SafelinkRule | 'ok'>({
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,
+53 -7
View File
@@ -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<BlinkMetricNames, MetricConfig>
export class AppContext {
cfg: Config
db: Database
metrics: Metrics = new Metrics()
metrics: Metrics<BlinkMetricConfig>
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<AppContextOptions>) {
+2 -1
View File
@@ -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)
}
+2 -1
View File
@@ -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()
}
+4 -1
View File
@@ -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
+4 -1
View File
@@ -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