import sqliteKv from "sqliteKv"; import { createClient } from "auth/client"; import axios from "axios"; import { consola } from "consola"; import { createDb, migrateToLatest } from "db"; import drizzle, { ensureWritable } from "drizzle"; import authVerifier from "lib/authVerifier"; import { env } from "lib/env"; import { createBidirectionalResolver, createIdResolver } from "lib/idResolver"; import { installPdsProxyFetch } from "lib/pdsProxy"; import { connect } from "nats"; import redis from "redis"; import { typesense } from "typesense/client"; import { createStorage } from "unstorage"; // Must run before anything builds an agent or an OAuth client: it swaps the // host of outbound requests to PDSes this server cannot reach directly. installPdsProxyFetch(); const { DB_PATH } = env; export const db = createDb(DB_PATH); await migrateToLatest(db); // Unlike the write-only Rust services, this one is mostly a read API, so a // read-only primary is logged as loudly as possible rather than made fatal — // taking the whole API down would be the larger outage. await ensureWritable().catch((err: Error) => { consola.error( `[db] PRIMARY IS NOT WRITABLE — every write will fail. ${err.message}`, ); }); const kv = createStorage({ driver: sqliteKv({ location: env.KV_DB_PATH, table: "kv" }), }); const baseIdResolver = createIdResolver(kv); export const ctx = { oauthClient: await createClient(db), resolver: createBidirectionalResolver(baseIdResolver), baseIdResolver, kv: new Map(), // Primary: writes, and reads that must be fresh. db: drizzle.db, // Read replica (the primary itself when unconfigured): lag-tolerant reads. readDb: drizzle.readDb, nc: await connect({ servers: env.NATS_URL }), analytics: axios.create({ baseURL: env.ANALYTICS }), tracklist: axios.create({ baseURL: env.TRACKLIST }), matching: axios.create({ baseURL: env.MATCHING_URL, timeout: 10_000 }), musicbrainz: axios.create({ baseURL: env.MUSICBRAINZ_URL }), deezer: axios.create({ baseURL: env.DEEZER_URL }), // The service queues for a rate limiter slot before answering, so the // timeout has to outlast its own queue budget (DISCOGS_MAX_WAIT). discogs: axios.create({ baseURL: env.DISCOGS_URL, timeout: 45_000 }), redis: await redis .createClient({ url: env.REDIS_URL }) .on("error", (err) => { consola.error("Uncaught Redis Client Error", err); process.exit(1); }) .connect(), typesense, authVerifier, sqliteDb: db, sqliteKv: kv, }; export type Context = typeof ctx;