UNPKG

@atproto/ozone

Version:

Backend service for moderating the Bluesky network.

234 lines 8.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Database = void 0; const node_assert_1 = __importDefault(require("node:assert")); const node_stream_1 = require("node:stream"); const kysely_1 = require("kysely"); const pg_1 = require("pg"); const logger_1 = require("../logger"); const migrations = __importStar(require("./migrations")); const provider_1 = require("./migrations/provider"); class Database { constructor(opts, instances) { Object.defineProperty(this, "opts", { enumerable: true, configurable: true, writable: true, value: opts }); Object.defineProperty(this, "pool", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "db", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "migrator", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "txEvt", { enumerable: true, configurable: true, writable: true, value: new node_stream_1.EventEmitter() }); Object.defineProperty(this, "destroyed", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "isPrimary", { enumerable: true, configurable: true, writable: true, value: false }); // if instances are provided, use those if (instances) { this.db = instances.db; this.pool = instances.pool; } else { // else create a pool & connect const { schema, url } = opts; const pool = opts.pool ?? new pg_1.Pool({ connectionString: url, max: opts.poolSize, maxUses: opts.poolMaxUses, idleTimeoutMillis: opts.poolIdleTimeoutMs, }); // Select count(*) and other pg bigints as js integer pg_1.types.setTypeParser(pg_1.types.builtins.INT8, (n) => parseInt(n, 10)); // Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema) if (schema && !/^[a-z_]+$/i.test(schema)) { throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`); } pool.on('error', onPoolError); pool.on('connect', (client) => { client.on('error', onClientError); // Used for trigram indexes, e.g. on actor search client.query('SET pg_trgm.word_similarity_threshold TO .4;'); if (schema) { // Shared objects such as extensions will go in the public schema client.query(`SET search_path TO "${schema}",public;`); } }); this.pool = pool; this.db = new kysely_1.Kysely({ dialect: new kysely_1.PostgresDialect({ pool }), }); } this.migrator = new kysely_1.Migrator({ db: this.db, migrationTableSchema: opts.schema, provider: new provider_1.CtxMigrationProvider(migrations, 'pg'), }); } get schema() { return this.opts.schema; } get isTransaction() { return this.db.isTransaction; } assertTransaction() { (0, node_assert_1.default)(this.isTransaction, 'Transaction required'); } assertNotTransaction() { (0, node_assert_1.default)(!this.isTransaction, 'Cannot be in a transaction'); } async transaction(fn) { const leakyTxPlugin = new LeakyTxPlugin(); const { dbTxn, txRes } = await this.db .withPlugin(leakyTxPlugin) .transaction() .execute(async (txn) => { const dbTxn = new Database(this.opts, { db: txn, pool: this.pool, }); const txRes = await fn(dbTxn) .catch(async (err) => { leakyTxPlugin.endTx(); // ensure that all in-flight queries are flushed & the connection is open await dbTxn.db.getExecutor().provideConnection(noopAsync); throw err; }) .finally(() => leakyTxPlugin.endTx()); return { dbTxn, txRes }; }); dbTxn?.txEvt.emit('commit'); return txRes; } onCommit(fn) { this.assertTransaction(); this.txEvt.once('commit', fn); } async close() { if (this.destroyed) return; await this.db.destroy(); this.destroyed = true; } async migrateToOrThrow(migration) { if (this.schema) { await this.db.schema.createSchema(this.schema).ifNotExists().execute(); } const { error, results } = await this.migrator.migrateTo(migration); if (error) { throw error; } if (!results) { throw new Error('An unknown failure occurred while migrating'); } return results; } async migrateToLatestOrThrow() { if (this.schema) { await this.db.schema.createSchema(this.schema).ifNotExists().execute(); } const { error, results } = await this.migrator.migrateToLatest(); if (error) { throw error; } if (!results) { throw new Error('An unknown failure occurred while migrating'); } return results; } } exports.Database = Database; exports.default = Database; const onPoolError = (err) => logger_1.dbLogger.error({ err }, 'db pool error'); const onClientError = (err) => logger_1.dbLogger.error({ err }, 'db client error'); // utils // ------- class LeakyTxPlugin { constructor() { Object.defineProperty(this, "txOver", { enumerable: true, configurable: true, writable: true, value: false }); } endTx() { this.txOver = true; } transformQuery(args) { if (this.txOver) { throw new Error('tx already failed'); } return args.node; } async transformResult(args) { return args.result; } } const noopAsync = async () => { }; //# sourceMappingURL=index.js.map