import {check} from '@augment-vir/assert';
import {log, type PartialWithUndefined} from '@augment-vir/common';
import {extractTestNameAsDir, type UniversalTestContext} from '@augment-vir/test';
import {type PGlite} from '@electric-sql/pglite';
import {existsSync} from 'node:fs';
import {join} from 'node:path';
import {resetPgliteDatabase} from '../migrations/migrate-reset.js';
import {getDefaultDbParentDirPath} from '../util/default-paths.js';
import {PrismaPGliteAdapterFactory} from './prisma-pglite-adapter/pglite.js';
/**
* Params for {@link createPgliteAdapter}.
*
* @category Internal
*/
export type PgliteAdapterParams = PartialWithUndefined<{
/**
* Path to a Prisma config file (`prisma.config.ts`).
*
* @default join(process.cwd(), 'prisma.config.ts')
*/
prismaConfigPath: string;
/**
* This is the path to your PGlite parent directory. Inside of this directory will be created
* the actual PGlite directories for each database name..
*
* @default
* - join('
', '.not-committed', 'pglite')
* - join(process.cwd(), '.not-committed', 'pglite')
*/
dbParentDirPath: string;
/**
* A optional name for the database. This allows separate prisma schemas to be used (for
* different databases) with the same configurations otherwise. If this is provided, the final
* database directory will be in `join(dbParentDirPath, , databaseName)`
*/
databaseName: string;
/**
* Overwrites `dbParentDirPath` and `testContext`, if either is provided, to provide a direct
* path to the PGlite database folder rather than deducing the folder path from
* `dbParentDirPath`.
*
* @default
* undefined
*/
directDatabaseDirPath: string;
/**
* Either a `UniversalTestContext` instance (which a dir name is extracted from), or a direct
* database dir name. This is primarily used for running unit tests with a new database per
* test, but can also be used to override the default `'dev'` database dir name. If this is
* provided, the final database directory will be in `join(dbParentDirPath, )`.
*
* @default undefined
*/
dbDirName: string | UniversalTestContext;
/**
* If set to true, any existing database at the database path will be deleted before setting up
* a new fresh instance.
*
* @default false
*/
resetDatabase: boolean;
}>;
/**
* Params for {@link PrismaPgliteAdapter}.
*
* @category Internal
*/
export type PrismaPgliteAdapterParams = {
wasJustInitialized: boolean;
databaseDirPath: string;
};
/**
* Extension of the core `PrismaPGlite` PGlite adapter that adds extra properties for external
* convenience.
*
* @category Internal
*/
export class PrismaPgliteAdapter extends PrismaPGliteAdapterFactory {
public readonly wasJustInitialized: boolean;
public readonly databaseDirPath: string;
constructor(pglite: PGlite, params: Readonly) {
super(pglite);
this.wasJustInitialized = params.wasJustInitialized;
this.databaseDirPath = params.databaseDirPath;
}
}
/**
* Creates a PGlite adapter than can be used with the `PrismaClient` constructor. This will create a
* new PGlite database on your file system, if one does not already exist, and push your schema to
* it (similar to `prisma db push`). This _cannot_, however, push new migrations to an existing
* PGlite database (as `prisma db push` does with a normal Postgres database).
*
* Requires Prisma v7 or later, where driver adapters are enabled by default (no preview feature is
* required).
*
* @category Adapter
* @example
*
* Usage in TypeScript:
*
* ```ts
* import {PrismaClient} from '../generated/client.js';
* import {createPgliteAdapter} from 'prisma-pglite';
*
* const prismaClient = new PrismaClient({
* adapter: await createPgliteAdapter({
* prismaConfigPath,
* }),
* });
* ```
*
* @example
*
* Requirement in `schema.prisma`:
*
* ```prisma
* generator jsClient {
* provider = "prisma-client"
* output = "../generated"
* }
* ```
*/
export async function createPgliteAdapter(
params: PgliteAdapterParams = {},
): Promise {
try {
/* node:coverage ignore next 1: this is not a branch operation */
const {PGlite} = await import('@electric-sql/pglite');
const dbDirName: string = check.isString(params.dbDirName)
? params.dbDirName
: params.dbDirName
? extractTestNameAsDir(params.dbDirName)
: 'dev';
const pathParts: string[] = [
params.dbParentDirPath || getDefaultDbParentDirPath(),
dbDirName,
params.databaseName || '',
].filter(check.isTruthy);
const databaseDirPath = params.directDatabaseDirPath || join(...pathParts);
const needsReset = params.resetDatabase || !existsSync(databaseDirPath);
const pglite = needsReset
? await resetPgliteDatabase({
pgliteDatabaseDirPath: databaseDirPath,
prismaConfigPath: params.prismaConfigPath,
})
: new PGlite(databaseDirPath);
await pglite.waitReady;
/**
* PGlite's WASM PostgreSQL startup sets process.exitCode as a side effect. Reset it after
* initialization completes so it doesn't cause Node.js test runner failures.
*/
process.exitCode = undefined;
return new PrismaPgliteAdapter(pglite, {
databaseDirPath,
wasJustInitialized: needsReset,
});
} catch (error) {
log.error(error);
/** Add our own error message because PGlite's error messages are really cryptic. */
throw new Error('Failed to initialize PGlite Prisma adapter', {
cause: error,
});
}
}