// @ts-check // import { SharedWorker } from 'ava/plugin'; const { randomBytes } = require('crypto'); const { Client } = require('pg'); const { createConnectionUrl } = require('../../../database'); const { config } = require('../../../config'); // import { // Data, // MessageType, // } from './types'; // type ReceivedMessage = SharedWorker.Experimental.ReceivedMessage; const factory: SharedWorker.Factory = async ({ negotiateProtocol }) => { const protocol = negotiateProtocol(['experimental']); protocol.ready(); for await (const message of protocol.subscribe()) { const { data } = message; if (data.type === MessageType.AQUIRE) { acquireDB(message); } } }; export default factory; const databases: Map = new Map(); let queue: { holderId: string; notify: (url: string) => void }[] = []; const MAX_DB_COUNT = 1; async function acquireDB(message: ReceivedMessage): Promise { const teardown = message.testWorker.teardown(async () => { const current = databases.get(message.id); databases if (current === undefined) { // remove any messages from queue queue = queue.filter(({ holderId }) => holderId !== message.id); return; } await tearDownDB(current.client); const next = queue.shift(); if (!next) { return; } // Generate DB for next in queue const dbClient = await buildDb(); databases.set( message.id, { holderId: message.id, client: dbClient } ) next.notify(getDbUri(dbClient)); }); if (databases.size >= MAX_DB_COUNT) { queue.push({ holderId: message.id, async notify(url: string) { for await (const { data } of message.reply({ url, type: MessageType.DB_ACQUIRED, }).replies()) { if (data.type === MessageType.DB_TEARDOWN) { teardown(); break; } } } }); } else { const dbClient = await buildDb(); databases.set( message.id, { holderId: message.id, client: dbClient } ) const replies = message.reply({ type: MessageType.DB_ACQUIRED, url: getDbUri(dbClient), }).replies() // send db for await (const { data } of replies) { if (data.type === MessageType.DB_TEARDOWN) { teardown(); break; } } } } export const buildDb = async () => { console.log(process.env.DATABASE_NAME); const templateName = `${process.env.DATABASE_NAME ?? 'generated'}__test`; const dbName = `${process.env.DATABASE_NAME ?? 'generated'}__test_${randomBytes(6).toString('hex')}`; const rootClient = new Client({ user: config.database.username, password: config.database.password, host: config.database.host, port: config.database.port, database: 'postgres', }); await rootClient.connect(); await rootClient.query(`CREATE DATABASE ${dbName} TEMPLATE ${templateName}`) await rootClient.end(); return new Client({ user: config.database.username, password: config.database.password, host: config.database.host, port: config.database.port, database: dbName, }) }; export const tearDownDB = async (client: Client) => { const dbname = client.database; if (!dbname) throw new Error('Database Name Not Defined'); console.log(`tearing down: ${dbname}`); const baseClient = new Client({ user: config.database.username, password: config.database.password, host: config.database.host, port: config.database.port, database: 'postgres', }); await baseClient.connect(); await baseClient.query(`DROP DATABASE IF EXISTS ${dbname}`) .catch((err) => { console.log(err) }) await baseClient.end(); }; function getDbUri(client: Client) { return createConnectionUrl({ username: client.user ?? 'postgres', password: client.password ?? 'postgres', host: client.host, port: client.port, name: client.database ?? 'postgres', }) }