From 97d2516fa98c4df621fbe91e31722a49ac1c137c Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 22:01:58 +0200 Subject: [PATCH 01/18] faster basic tests by not running initdb each time --- packages/pglite/tests/basic.test.ts | 44 ++++++++++++++++------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/pglite/tests/basic.test.ts b/packages/pglite/tests/basic.test.ts index b845b4f1f..b9d7c714a 100644 --- a/packages/pglite/tests/basic.test.ts +++ b/packages/pglite/tests/basic.test.ts @@ -1,6 +1,7 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { expectToThrowAsync, testEsmCjsAndDTC } from './test-utils.ts' import { identifier } from '../dist/templating.js' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -11,8 +12,28 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/index.js')) describe(`basic`, () => { + + let db: PGlite + let dataDirArchive: File | Blob + + beforeEach(async () => { + if (!dataDirArchive) { + db = await PGlite.create() + dataDirArchive = await db.dumpDataDir('gzip') + } else { + db = await PGlite.create({ + loadDataDir: dataDirArchive + }) + } + }) + + afterEach(async () => { + if (!db.closed) { + await db.close() + } + }) + it('exec', async () => { - const db = await PGlite.create() await db.exec(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -46,12 +67,9 @@ await testEsmCjsAndDTC(async (importType) => { affectedRows: 2, }, ]) - - await db.close() }) it('query', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -92,7 +110,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('query templated', async () => { - const db = new PGlite() const tableName = identifier`test` await db.sql` CREATE TABLE IF NOT EXISTS ${tableName} ( @@ -133,7 +150,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('types', async () => { - const db = await PGlite.create() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -295,6 +311,7 @@ await testEsmCjsAndDTC(async (importType) => { it('custom parser and serializer', async () => { const db = new PGlite({ + loadDataDir: dataDirArchive, serializers: { 1700: (x) => x.toString() }, parsers: { 1700: (x) => BigInt(x) }, }) @@ -331,7 +348,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('params', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -365,7 +381,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('array params', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -418,14 +433,12 @@ await testEsmCjsAndDTC(async (importType) => { }) it('error', async () => { - const db = await PGlite.create() await expectToThrowAsync(async () => { await db.query('SELECT * FROM test;') }, 'relation "test" does not exist') }) it('transaction', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -487,7 +500,6 @@ await testEsmCjsAndDTC(async (importType) => { }) }) it('merge delete', async () => { - const db = new PGlite() await db.exec(` CREATE TABLE employees ( id SERIAL PRIMARY KEY, @@ -520,7 +532,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('copy to/from blob', async () => { - const db = new PGlite() await db.exec(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -588,7 +599,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('close', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -603,7 +613,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('use same param multiple times', async () => { - const db = new PGlite() await db.exec(` CREATE TABLE IF NOT EXISTS test ( @@ -630,7 +639,6 @@ await testEsmCjsAndDTC(async (importType) => { }) }) it('timezone', async () => { - const db = new PGlite() const res = await db.query( `SELECT now(),* FROM pg_timezone_names WHERE name = current_setting('TIMEZONE')`, @@ -639,7 +647,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('default database, user and role should be "postgres"', async () => { - const db = await PGlite.create() const databaseAndRole = await db.exec( `SELECT current_database(), current_user, current_role;`, @@ -664,7 +671,6 @@ await testEsmCjsAndDTC(async (importType) => { // this tests the parameter 'max_parallel_workers_per_gather=0', it('it shouldnt use parallel workers on gather', async () => { - const db = await PGlite.create() const ROWS = 400_000 @@ -704,7 +710,7 @@ await testEsmCjsAndDTC(async (importType) => { it('restores process.exitCode', async () => { const origExitCode = process.exitCode - const db = await PGlite.create() + expect(process.exitCode).toEqual(origExitCode) await db.exec(` From a6c3b82aeb5e50adb5bdf7ab75490d4a24443b69 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 22:02:17 +0200 Subject: [PATCH 02/18] style --- packages/pglite/tests/basic.test.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/pglite/tests/basic.test.ts b/packages/pglite/tests/basic.test.ts index b9d7c714a..c5666c5d5 100644 --- a/packages/pglite/tests/basic.test.ts +++ b/packages/pglite/tests/basic.test.ts @@ -12,7 +12,6 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/index.js')) describe(`basic`, () => { - let db: PGlite let dataDirArchive: File | Blob @@ -22,7 +21,7 @@ await testEsmCjsAndDTC(async (importType) => { dataDirArchive = await db.dumpDataDir('gzip') } else { db = await PGlite.create({ - loadDataDir: dataDirArchive + loadDataDir: dataDirArchive, }) } }) @@ -613,7 +612,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('use same param multiple times', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -639,7 +637,6 @@ await testEsmCjsAndDTC(async (importType) => { }) }) it('timezone', async () => { - const res = await db.query( `SELECT now(),* FROM pg_timezone_names WHERE name = current_setting('TIMEZONE')`, ) @@ -647,7 +644,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('default database, user and role should be "postgres"', async () => { - const databaseAndRole = await db.exec( `SELECT current_database(), current_user, current_role;`, ) @@ -671,7 +667,6 @@ await testEsmCjsAndDTC(async (importType) => { // this tests the parameter 'max_parallel_workers_per_gather=0', it('it shouldnt use parallel workers on gather', async () => { - const ROWS = 400_000 await db.exec(` @@ -710,7 +705,7 @@ await testEsmCjsAndDTC(async (importType) => { it('restores process.exitCode', async () => { const origExitCode = process.exitCode - + expect(process.exitCode).toEqual(origExitCode) await db.exec(` From b98e4f59f08d3c7f6069ad0cbd0d7d2589c64756 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 22:13:24 +0200 Subject: [PATCH 03/18] same as above in live tests --- packages/pglite/tests/live.test.ts | 84 +++++++++--------------------- 1 file changed, 24 insertions(+), 60 deletions(-) diff --git a/packages/pglite/tests/live.test.ts b/packages/pglite/tests/live.test.ts index f955ee9d7..92b5d0e78 100644 --- a/packages/pglite/tests/live.test.ts +++ b/packages/pglite/tests/live.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGliteWithLive } from '../dist/live/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = ( @@ -14,11 +15,29 @@ await testEsmCjsAndDTC(async (importType) => { : await import('../dist/live/index.cjs') describe(`live`, () => { - it('basic live query', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) + let db: PGliteWithLive + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + db = await PGlite.create({ + extensions: { live }, + }) + dataDirArchive = await db.dumpDataDir('gzip') + } else { + db = await PGlite.create({ + extensions: { live }, + loadDataDir: dataDirArchive, + }) + } + }) + afterEach(async () => { + if (!db.closed) { + await db.close() + } + }) + + it('basic live query', async () => { await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -110,10 +129,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live query on view', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -220,10 +235,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live query with params', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -305,10 +316,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('incremental query unordered', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -354,10 +361,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('incremental query with non-integer key', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id TEXT PRIMARY KEY, @@ -403,9 +406,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live incremental query', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -499,9 +499,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live incremental query with limit 1', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -541,9 +538,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live incremental query on view', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -652,9 +646,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live incremental query with params', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -738,9 +729,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live changes', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -913,9 +901,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('subscribe to live query after creation', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -969,9 +954,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live changes limit 1', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -1035,9 +1017,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('subscribe to live changes after creation', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -1094,9 +1073,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live query with windowing', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -1185,9 +1161,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('throws error when only one of offset/limit is provided', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await expect( db.live.query({ @@ -1205,9 +1178,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('throws error when offset/limit are not numbers', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await expect( db.live.query({ @@ -1227,9 +1197,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it("doesn't have a race condition when unsubscribing from a live query", async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( @@ -1274,9 +1241,6 @@ await testEsmCjsAndDTC(async (importType) => { }, 3000) it('works with pattern matching', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( From 8e238f078a9788de15982ba3f2acc2e15fd1f73a Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 22:23:03 +0200 Subject: [PATCH 04/18] same as above for pgcrypto --- .../{pgcrypto.test.js => pgcrypto.test.ts} | 132 +++++------------- packages/pglite/tests/live.test.ts | 13 -- 2 files changed, 33 insertions(+), 112 deletions(-) rename packages/pglite/tests/contrib/{pgcrypto.test.js => pgcrypto.test.ts} (71%) diff --git a/packages/pglite/tests/contrib/pgcrypto.test.js b/packages/pglite/tests/contrib/pgcrypto.test.ts similarity index 71% rename from packages/pglite/tests/contrib/pgcrypto.test.js rename to packages/pglite/tests/contrib/pgcrypto.test.ts index a60d577b6..5323e476b 100644 --- a/packages/pglite/tests/contrib/pgcrypto.test.js +++ b/packages/pglite/tests/contrib/pgcrypto.test.ts @@ -1,18 +1,36 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '../../dist/index.js' import { pgcrypto } from '../../dist/contrib/pgcrypto.js' import * as openpgp from 'openpgp' describe('pg_pgcryptotrgm', () => { - it('digest', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) + let pg: PGlite + let dataDirArchive: File | Blob + + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pgcrypto }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pgcrypto }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + it('digest', async () => { + const res = await pg.query( "SELECT encode(digest(convert_to('test', 'UTF8'), 'sha1'), 'hex') as value;", ) @@ -20,14 +38,7 @@ describe('pg_pgcryptotrgm', () => { }) it('hmac', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + const res = await pg.query( "SELECT encode(hmac(convert_to('test', 'UTF8'), convert_to('key', 'UTF8'), 'sha1'), 'hex') as value;", ) @@ -37,54 +48,26 @@ describe('pg_pgcryptotrgm', () => { }) it('crypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + const res = await pg.query("SELECT crypt('test', gen_salt('bf')) as value;") expect(res.rows[0].value.length).toEqual(60) }) it('gen_salt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + const res = await pg.query("SELECT gen_salt('bf') as value;") expect(res.rows[0].value.length).toEqual(29) }) it('armor', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + const res = await pg.query("SELECT armor(digest('test', 'sha1')) as value;") expect(res.rows[0].value).toContain('-----BEGIN PGP MESSAGE-----') expect(res.rows[0].value).toContain('-----END PGP MESSAGE-----') }) it('pgp_sym_encrypt and pgp_sym_decrypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + const res = await pg.query( "SELECT pgp_sym_encrypt('test', 'key') as value;", ) @@ -97,14 +80,7 @@ describe('pg_pgcryptotrgm', () => { }) it('pgp_pub_encrypt and pgp_pub_decrypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + const { privateKey, publicKey } = await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, @@ -128,14 +104,7 @@ FROM encrypted; }) it('pgp_key_id', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + const { publicKey } = await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, @@ -152,14 +121,7 @@ FROM encrypted; }) it('pgp_armor_headers', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - + // Create armored data with headers const res = await pg.query( `SELECT armor(digest('test', 'sha1'), ARRAY['key1'], ARRAY['value1']) as armored;`, @@ -173,13 +135,6 @@ FROM encrypted; }) it('encrypt and decrypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') const res = await pg.query( `SELECT encrypt('test data'::bytea, 'secret key'::bytea, 'aes') as encrypted;`, @@ -194,13 +149,6 @@ FROM encrypted; }) it('encrypt_iv and decrypt_iv', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') // AES block size is 16 bytes, so IV must be 16 bytes const iv = '1234567890123456' @@ -218,13 +166,6 @@ FROM encrypted; }) it('gen_random_bytes', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') const res = await pg.query( `SELECT length(gen_random_bytes(32)) as len, encode(gen_random_bytes(16), 'hex') as bytes;`, @@ -235,13 +176,6 @@ FROM encrypted; }) it('gen_random_uuid', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') const res = await pg.query(`SELECT gen_random_uuid() as uuid;`) // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx diff --git a/packages/pglite/tests/live.test.ts b/packages/pglite/tests/live.test.ts index 92b5d0e78..afd3f3cf3 100644 --- a/packages/pglite/tests/live.test.ts +++ b/packages/pglite/tests/live.test.ts @@ -406,7 +406,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live incremental query', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -499,7 +498,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live incremental query with limit 1', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -538,7 +536,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live incremental query on view', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -646,7 +643,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live incremental query with params', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -729,7 +725,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live changes', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -901,7 +896,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('subscribe to live query after creation', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -954,7 +948,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live changes limit 1', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1017,7 +1010,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('subscribe to live changes after creation', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1073,7 +1065,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live query with windowing', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1161,7 +1152,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('throws error when only one of offset/limit is provided', async () => { - await expect( db.live.query({ query: 'SELECT * FROM (VALUES (1)) t', @@ -1178,7 +1168,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('throws error when offset/limit are not numbers', async () => { - await expect( db.live.query({ query: 'SELECT * FROM (VALUES (1)) t', @@ -1197,7 +1186,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it("doesn't have a race condition when unsubscribing from a live query", async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1241,7 +1229,6 @@ await testEsmCjsAndDTC(async (importType) => { }, 3000) it('works with pattern matching', async () => { - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, From ddb01a591092ec4ebd35c0971525670a374f9dd3 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 22:23:24 +0200 Subject: [PATCH 05/18] style --- packages/pglite/tests/contrib/pgcrypto.test.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/pglite/tests/contrib/pgcrypto.test.ts b/packages/pglite/tests/contrib/pgcrypto.test.ts index 5323e476b..a03f558c5 100644 --- a/packages/pglite/tests/contrib/pgcrypto.test.ts +++ b/packages/pglite/tests/contrib/pgcrypto.test.ts @@ -4,7 +4,6 @@ import { pgcrypto } from '../../dist/contrib/pgcrypto.js' import * as openpgp from 'openpgp' describe('pg_pgcryptotrgm', () => { - let pg: PGlite let dataDirArchive: File | Blob @@ -30,7 +29,6 @@ describe('pg_pgcryptotrgm', () => { }) it('digest', async () => { - const res = await pg.query( "SELECT encode(digest(convert_to('test', 'UTF8'), 'sha1'), 'hex') as value;", ) @@ -38,7 +36,6 @@ describe('pg_pgcryptotrgm', () => { }) it('hmac', async () => { - const res = await pg.query( "SELECT encode(hmac(convert_to('test', 'UTF8'), convert_to('key', 'UTF8'), 'sha1'), 'hex') as value;", ) @@ -48,26 +45,22 @@ describe('pg_pgcryptotrgm', () => { }) it('crypt', async () => { - const res = await pg.query("SELECT crypt('test', gen_salt('bf')) as value;") expect(res.rows[0].value.length).toEqual(60) }) it('gen_salt', async () => { - const res = await pg.query("SELECT gen_salt('bf') as value;") expect(res.rows[0].value.length).toEqual(29) }) it('armor', async () => { - const res = await pg.query("SELECT armor(digest('test', 'sha1')) as value;") expect(res.rows[0].value).toContain('-----BEGIN PGP MESSAGE-----') expect(res.rows[0].value).toContain('-----END PGP MESSAGE-----') }) it('pgp_sym_encrypt and pgp_sym_decrypt', async () => { - const res = await pg.query( "SELECT pgp_sym_encrypt('test', 'key') as value;", ) @@ -80,7 +73,6 @@ describe('pg_pgcryptotrgm', () => { }) it('pgp_pub_encrypt and pgp_pub_decrypt', async () => { - const { privateKey, publicKey } = await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, @@ -104,7 +96,6 @@ FROM encrypted; }) it('pgp_key_id', async () => { - const { publicKey } = await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, @@ -121,7 +112,6 @@ FROM encrypted; }) it('pgp_armor_headers', async () => { - // Create armored data with headers const res = await pg.query( `SELECT armor(digest('test', 'sha1'), ARRAY['key1'], ARRAY['value1']) as armored;`, @@ -135,7 +125,6 @@ FROM encrypted; }) it('encrypt and decrypt', async () => { - const res = await pg.query( `SELECT encrypt('test data'::bytea, 'secret key'::bytea, 'aes') as encrypted;`, ) @@ -149,7 +138,6 @@ FROM encrypted; }) it('encrypt_iv and decrypt_iv', async () => { - // AES block size is 16 bytes, so IV must be 16 bytes const iv = '1234567890123456' @@ -166,7 +154,6 @@ FROM encrypted; }) it('gen_random_bytes', async () => { - const res = await pg.query( `SELECT length(gen_random_bytes(32)) as len, encode(gen_random_bytes(16), 'hex') as bytes;`, ) @@ -176,7 +163,6 @@ FROM encrypted; }) it('gen_random_uuid', async () => { - const res = await pg.query(`SELECT gen_random_uuid() as uuid;`) // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx expect(res.rows[0].uuid).toMatch( From 5928cf55657ffda5eb55fa71d8bb0d253b152c92 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:21:14 +0200 Subject: [PATCH 06/18] same as above for age extension --- packages/pglite/tests/age.test.ts | 161 ++++++------------------------ 1 file changed, 31 insertions(+), 130 deletions(-) diff --git a/packages/pglite/tests/age.test.ts b/packages/pglite/tests/age.test.ts index fff679c72..d1dfc54cb 100644 --- a/packages/pglite/tests/age.test.ts +++ b/packages/pglite/tests/age.test.ts @@ -16,8 +16,9 @@ * ``` */ -import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -35,21 +36,41 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/age/index.js')) describe(`age (${importType})`, () => { - // ========================================================================= - // BASIC EXTENSION LOADING - // ========================================================================= - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { age }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { age }, + loadDataDir: dataDirArchive, + }) + } + await pg.exec(` CREATE EXTENSION IF NOT EXISTS age; LOAD 'age'; SET search_path = ag_catalog, "$user", public; `) + }) + + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + + // ========================================================================= + // BASIC EXTENSION LOADING + // ========================================================================= + + it('can load extension', async () => { const res = await pg.query<{ extname: string }>(` SELECT extname FROM pg_extension WHERE extname = 'age' @@ -65,16 +86,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can create a graph', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) // Create a new graph using ag_catalog.create_graph() // This creates the graph metadata and necessary internal tables @@ -91,16 +102,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can drop graph', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) // Create and then drop a graph await pg.exec("SELECT ag_catalog.create_graph('temp_graph');") @@ -120,16 +121,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can execute cypher CREATE and MATCH', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('cypher_test');") @@ -163,16 +154,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can create edges between nodes', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('edge_test');") @@ -206,16 +187,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('hooks are active - cypher syntax parses correctly', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('hook_test');") @@ -238,16 +209,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can use WHERE clause in MATCH', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('where_test');") @@ -282,16 +243,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('EXPLAIN works on cypher queries', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('explain_test');") @@ -313,16 +264,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('handles unicode in properties', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('unicode_test');") @@ -355,16 +296,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('handles invalid cypher syntax gracefully', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('error_test');") @@ -385,16 +316,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can update node properties', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('update_test');") @@ -433,16 +354,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can delete nodes', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('delete_test');") @@ -480,16 +391,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can use ORDER BY and LIMIT', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) await pg.exec("SELECT ag_catalog.create_graph('order_test');") From b91853ff345cd5b4a72640362d239e30fbe589a6 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:24:45 +0200 Subject: [PATCH 07/18] same as above for pg_hashids extension tests --- packages/pglite/tests/age.test.ts | 25 ++---- packages/pglite/tests/pg_hashids.test.ts | 102 ++++++----------------- 2 files changed, 34 insertions(+), 93 deletions(-) diff --git a/packages/pglite/tests/age.test.ts b/packages/pglite/tests/age.test.ts index d1dfc54cb..915eb5b24 100644 --- a/packages/pglite/tests/age.test.ts +++ b/packages/pglite/tests/age.test.ts @@ -16,7 +16,15 @@ * ``` */ -import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest' +import { + describe, + it, + expect, + beforeAll, + afterAll, + beforeEach, + afterEach, +} from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' import { PGlite } from '../dist/index.js' @@ -36,7 +44,6 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/age/index.js')) describe(`age (${importType})`, () => { - let pg: PGlite let dataDirArchive: File | Blob beforeEach(async () => { @@ -65,13 +72,11 @@ await testEsmCjsAndDTC(async (importType) => { } }) - // ========================================================================= // BASIC EXTENSION LOADING // ========================================================================= it('can load extension', async () => { - const res = await pg.query<{ extname: string }>(` SELECT extname FROM pg_extension WHERE extname = 'age' `) @@ -86,7 +91,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can create a graph', async () => { - // Create a new graph using ag_catalog.create_graph() // This creates the graph metadata and necessary internal tables await pg.exec("SELECT ag_catalog.create_graph('test_graph');") @@ -102,7 +106,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can drop graph', async () => { - // Create and then drop a graph await pg.exec("SELECT ag_catalog.create_graph('temp_graph');") await pg.exec("SELECT ag_catalog.drop_graph('temp_graph', true);") @@ -121,7 +124,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can execute cypher CREATE and MATCH', async () => { - await pg.exec("SELECT ag_catalog.create_graph('cypher_test');") // CREATE a node with a label and properties @@ -154,7 +156,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can create edges between nodes', async () => { - await pg.exec("SELECT ag_catalog.create_graph('edge_test');") // Create a full path: two nodes connected by an edge @@ -187,7 +188,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('hooks are active - cypher syntax parses correctly', async () => { - await pg.exec("SELECT ag_catalog.create_graph('hook_test');") // This query uses Cypher-specific syntax that PostgreSQL @@ -209,7 +209,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can use WHERE clause in MATCH', async () => { - await pg.exec("SELECT ag_catalog.create_graph('where_test');") // Create multiple nodes @@ -243,7 +242,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('EXPLAIN works on cypher queries', async () => { - await pg.exec("SELECT ag_catalog.create_graph('explain_test');") // EXPLAIN shows the query execution plan @@ -264,7 +262,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('handles unicode in properties', async () => { - await pg.exec("SELECT ag_catalog.create_graph('unicode_test');") // Create node with unicode properties @@ -296,7 +293,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('handles invalid cypher syntax gracefully', async () => { - await pg.exec("SELECT ag_catalog.create_graph('error_test');") // Invalid Cypher syntax should throw an error @@ -316,7 +312,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can update node properties', async () => { - await pg.exec("SELECT ag_catalog.create_graph('update_test');") // Create a node @@ -354,7 +349,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can delete nodes', async () => { - await pg.exec("SELECT ag_catalog.create_graph('delete_test');") // Create nodes @@ -391,7 +385,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can use ORDER BY and LIMIT', async () => { - await pg.exec("SELECT ag_catalog.create_graph('order_test');") // Create multiple nodes with different ages diff --git a/packages/pglite/tests/pg_hashids.test.ts b/packages/pglite/tests/pg_hashids.test.ts index 61224c857..745c73383 100644 --- a/packages/pglite/tests/pg_hashids.test.ts +++ b/packages/pglite/tests/pg_hashids.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -17,14 +18,31 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pg_hashids/index.js')) describe(`pg_hashids`, () => { - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pg_hashids }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pg_hashids }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') + }) + + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + it('can load extension', async () => { const res = await pg.query<{ extname: string }>(` SELECT extname @@ -37,13 +55,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the default alphabet and empty salt', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec(`SELECT id_encode(1001);`) @@ -51,13 +62,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the default alphabet and supplied salt', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec(`SELECT id_encode(1234567, 'This is my salt');`) @@ -65,13 +69,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the default alphabet, salt and minimum hash length', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec( `SELECT id_encode(1234567, 'This is my salt', 10);`, @@ -81,13 +78,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the supplied alphabet, salt and minimum hash length', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec( `SELECT id_encode(1234567, 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, @@ -97,13 +87,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec( `SELECT id_decode('PlRPdzxpR7', 'This is my salt', 10);`, @@ -113,13 +96,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash using the supplied alphabet', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec( `SELECT id_decode('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, @@ -129,13 +105,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec(`SELECT id_decode_once('jNl');`) @@ -143,13 +112,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer using the supplied salt', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec( `SELECT id_decode_once('Pdzxp', 'This is my salt');`, @@ -159,13 +121,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer using the supplied salt and minimum hash length', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec( `SELECT id_decode_once('PlRPdzxpR7', 'This is my salt', 10);`, @@ -175,13 +130,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer using the supplied alphabet', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') const res = await pg.exec( `SELECT id_decode_once('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, From f7204ef670605239a6bc11566c77d87df2709735 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:25:02 +0200 Subject: [PATCH 08/18] style; --- packages/pglite/tests/pg_hashids.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/pglite/tests/pg_hashids.test.ts b/packages/pglite/tests/pg_hashids.test.ts index 745c73383..82b1ed2af 100644 --- a/packages/pglite/tests/pg_hashids.test.ts +++ b/packages/pglite/tests/pg_hashids.test.ts @@ -18,7 +18,6 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pg_hashids/index.js')) describe(`pg_hashids`, () => { - let pg: PGlite let dataDirArchive: File | Blob beforeEach(async () => { @@ -43,7 +42,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can load extension', async () => { - const res = await pg.query<{ extname: string }>(` SELECT extname FROM pg_extension @@ -55,21 +53,18 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the default alphabet and empty salt', async () => { - const res = await pg.exec(`SELECT id_encode(1001);`) expect(res[0].rows[0].id_encode).toEqual('jNl') }) it('should return a hash using the default alphabet and supplied salt', async () => { - const res = await pg.exec(`SELECT id_encode(1234567, 'This is my salt');`) expect(res[0].rows[0].id_encode).toEqual('Pdzxp') }) it('should return a hash using the default alphabet, salt and minimum hash length', async () => { - const res = await pg.exec( `SELECT id_encode(1234567, 'This is my salt', 10);`, ) @@ -78,7 +73,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the supplied alphabet, salt and minimum hash length', async () => { - const res = await pg.exec( `SELECT id_encode(1234567, 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, ) @@ -87,7 +81,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash', async () => { - const res = await pg.exec( `SELECT id_decode('PlRPdzxpR7', 'This is my salt', 10);`, ) @@ -96,7 +89,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash using the supplied alphabet', async () => { - const res = await pg.exec( `SELECT id_decode('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, ) @@ -105,14 +97,12 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer', async () => { - const res = await pg.exec(`SELECT id_decode_once('jNl');`) expect(res[0].rows[0].id_decode_once).toEqual(1001) }) it('should decode previously generated hash into a single integer using the supplied salt', async () => { - const res = await pg.exec( `SELECT id_decode_once('Pdzxp', 'This is my salt');`, ) @@ -121,7 +111,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer using the supplied salt and minimum hash length', async () => { - const res = await pg.exec( `SELECT id_decode_once('PlRPdzxpR7', 'This is my salt', 10);`, ) @@ -130,7 +119,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer using the supplied alphabet', async () => { - const res = await pg.exec( `SELECT id_decode_once('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, ) From 3a881dd797109107504645e928fea627d045463a Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:29:31 +0200 Subject: [PATCH 09/18] style --- packages/pglite/tests/pg_ivm.test.ts | 68 ++++++++++------------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/packages/pglite/tests/pg_ivm.test.ts b/packages/pglite/tests/pg_ivm.test.ts index 60c7aad57..4782d9319 100644 --- a/packages/pglite/tests/pg_ivm.test.ts +++ b/packages/pglite/tests/pg_ivm.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -17,15 +18,30 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pg_ivm/index.js')) describe(`pg_ivm`, () => { - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pg_ivm }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pg_ivm }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + it('can load extension', async () => { // Verify the extension is loaded const res = await pg.query<{ extname: string }>(` SELECT extname @@ -38,14 +54,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can create incremental materialized view', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') - // Create base table await pg.exec(` CREATE TABLE orders ( @@ -83,13 +91,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('automatically updates view when base table changes', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') // Create base table await pg.exec(` @@ -208,13 +209,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('supports simple views without aggregates', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') // Create base tables await pg.exec(` @@ -301,13 +295,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('supports DISTINCT in views', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') // Create base table with potential duplicates await pg.exec(` @@ -377,13 +364,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can use refresh_immv function', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') // Create base table await pg.exec(` From ebd97c16485f4db5299700eb79dba71ea40bba23 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:30:39 +0200 Subject: [PATCH 10/18] style; --- packages/pglite/tests/pg_ivm.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/pglite/tests/pg_ivm.test.ts b/packages/pglite/tests/pg_ivm.test.ts index 4782d9319..d5fea80bc 100644 --- a/packages/pglite/tests/pg_ivm.test.ts +++ b/packages/pglite/tests/pg_ivm.test.ts @@ -40,7 +40,7 @@ await testEsmCjsAndDTC(async (importType) => { await pg.close() } }) - + it('can load extension', async () => { // Verify the extension is loaded const res = await pg.query<{ extname: string }>(` @@ -91,7 +91,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('automatically updates view when base table changes', async () => { - // Create base table await pg.exec(` CREATE TABLE products ( @@ -209,7 +208,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('supports simple views without aggregates', async () => { - // Create base tables await pg.exec(` CREATE TABLE users ( @@ -295,7 +293,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('supports DISTINCT in views', async () => { - // Create base table with potential duplicates await pg.exec(` CREATE TABLE events ( @@ -364,7 +361,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can use refresh_immv function', async () => { - // Create base table await pg.exec(` CREATE TABLE items ( From b330fc23d994b217e1f2b696b1c08fc45f62f63f Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:33:08 +0200 Subject: [PATCH 11/18] same as above for uuid_ossp extension tests --- .../{uuid_ossp.test.js => uuid_ossp.test.ts} | 78 ++++++------------- 1 file changed, 22 insertions(+), 56 deletions(-) rename packages/pglite/tests/contrib/{uuid_ossp.test.js => uuid_ossp.test.ts} (58%) diff --git a/packages/pglite/tests/contrib/uuid_ossp.test.js b/packages/pglite/tests/contrib/uuid_ossp.test.ts similarity index 58% rename from packages/pglite/tests/contrib/uuid_ossp.test.js rename to packages/pglite/tests/contrib/uuid_ossp.test.ts index 6ca486692..b644d68e0 100644 --- a/packages/pglite/tests/contrib/uuid_ossp.test.js +++ b/packages/pglite/tests/contrib/uuid_ossp.test.ts @@ -1,31 +1,37 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '../../dist/index.js' import { uuid_ossp } from '../../dist/contrib/uuid_ossp.js' describe('uuid_ossp', () => { - it('uuid_generate_v1', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { uuid_ossp }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { uuid_ossp }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + it('uuid_generate_v1', async () => { const res = await pg.query('SELECT uuid_generate_v1() as value;') expect(res.rows[0].value.length).toBe(36) }) it('uuid_generate_v3', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query( "SELECT uuid_generate_v3(uuid_ns_dns(), 'www.example.com') as value;", ) @@ -34,28 +40,12 @@ describe('uuid_ossp', () => { }) it('uuid_generate_v4', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_generate_v4() as value;') expect(res.rows[0].value.length).toBe(36) }) it('uuid_generate_v5', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query( "SELECT uuid_generate_v5(uuid_ns_dns(), 'www.example.com') as value;", ) @@ -64,42 +54,18 @@ describe('uuid_ossp', () => { }) it('uuid_nil', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_nil() as value;') expect(res.rows[0].value).toBe('00000000-0000-0000-0000-000000000000') }) it('uuid_ns_dns', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_ns_dns() as value;') expect(res.rows[0].value).toBe('6ba7b810-9dad-11d1-80b4-00c04fd430c8') }) it('uuid_ns_oid', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_ns_oid() as value;') expect(res.rows[0].value).toBe('6ba7b812-9dad-11d1-80b4-00c04fd430c8') From 7d5a55676b896e55dabb6c70da806873f67e0ca3 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:36:03 +0200 Subject: [PATCH 12/18] same as above for pgtap extension tests --- packages/pglite/tests/pgtap.test.ts | 71 ++++++++++------------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/packages/pglite/tests/pgtap.test.ts b/packages/pglite/tests/pgtap.test.ts index 1f4f12edf..637d1ace1 100644 --- a/packages/pglite/tests/pgtap.test.ts +++ b/packages/pglite/tests/pgtap.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -17,15 +18,29 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pgtap/index.js')) describe(`pgtap`, () => { - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pgtap }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pgtap }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + it('can load extension', async () => { // Verify the extension is loaded const res = await pg.query<{ extname: string }>(` SELECT extname @@ -38,14 +53,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run individual pgTAP assertions', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` -- Start transaction and plan the tests. BEGIN; @@ -69,14 +76,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should check for correct amounts of tests', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` BEGIN; SELECT plan(1); -- wrong amount of tests @@ -98,14 +97,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run multiple tests', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` -- Start transaction and plan the tests. BEGIN; @@ -148,14 +139,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run pgTAP test suite', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` BEGIN; CREATE TABLE users ( @@ -193,14 +176,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run in-depth assertion tests', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` BEGIN; From 3e8920759ee010598fba788124c289003a6406ce Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:40:05 +0200 Subject: [PATCH 13/18] same as above for postgis extension tests --- packages/pglite-postgis/tests/postgis.test.ts | 273 ++++++++---------- 1 file changed, 127 insertions(+), 146 deletions(-) diff --git a/packages/pglite-postgis/tests/postgis.test.ts b/packages/pglite-postgis/tests/postgis.test.ts index d3f4c805b..e578889d7 100644 --- a/packages/pglite-postgis/tests/postgis.test.ts +++ b/packages/pglite-postgis/tests/postgis.test.ts @@ -1,16 +1,31 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '@electric-sql/pglite' import { postgis } from '../src/index.js' describe(`postgis`, () => { - it('basic', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { postgis }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { postgis }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + it('basic', async () => { await pg.exec(` CREATE TABLE vehicle_location ( time TIMESTAMPTZ NOT NULL, @@ -26,13 +41,6 @@ describe(`postgis`, () => { expect(inserted.affectedRows).toEqual(3) }), it('cities', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') await pg.exec(` CREATE TABLE cities ( id SERIAL PRIMARY KEY, @@ -62,17 +70,8 @@ WHERE ST_Within(c.location, s.geom);`) name: 'Chicago', }) }) -}) - -it('areas', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - - const area1 = await pg.exec(` + it('areas', async () => { + const area1 = await pg.exec(` select ST_Area(geom) sqft, ST_Area(geom) * 0.3048 ^ 2 sqm from ( @@ -80,29 +79,29 @@ it('areas', async () => { 743265 2967450,743265.625 2967416,743238 2967416))' :: geometry geom ) subquery;`) - expect(area1).toEqual([ - { - rows: [ - { - sqft: 928.625, - sqm: 86.27208552, - }, - ], - fields: [ - { - name: 'sqft', - dataTypeID: 701, - }, - { - name: 'sqm', - dataTypeID: 701, - }, - ], - affectedRows: 0, - }, - ]) + expect(area1).toEqual([ + { + rows: [ + { + sqft: 928.625, + sqm: 86.27208552, + }, + ], + fields: [ + { + name: 'sqft', + dataTypeID: 701, + }, + { + name: 'sqm', + dataTypeID: 701, + }, + ], + affectedRows: 0, + }, + ]) - const area2 = await pg.exec(` + const area2 = await pg.exec(` select ST_Area(geom) sqft, ST_Area(ST_Transform(geom, 26986)) As sqm from ( @@ -115,29 +114,29 @@ it('areas', async () => { -- DROP SCHEMA postgis_test CASCADE; `) - expect(area2).toEqual([ - { - rows: [ - { - sqft: 928.625, - sqm: 86.27243061926092, - }, - ], - fields: [ - { - name: 'sqft', - dataTypeID: 701, - }, - { - name: 'sqm', - dataTypeID: 701, - }, - ], - affectedRows: 0, - }, - ]) + expect(area2).toEqual([ + { + rows: [ + { + sqft: 928.625, + sqm: 86.27243061926092, + }, + ], + fields: [ + { + name: 'sqft', + dataTypeID: 701, + }, + { + name: 'sqm', + dataTypeID: 701, + }, + ], + affectedRows: 0, + }, + ]) - const area3 = await pg.exec(` + const area3 = await pg.exec(` select ST_Area(geog) / 0.3048 ^ 2 sqft_spheroid, ST_Area(geog, false) / 0.3048 ^ 2 sqft_sphere, ST_Area(geog) sqm_spheroid @@ -149,42 +148,36 @@ it('areas', async () => { ) as subquery; `) - expect(area3).toEqual([ - { - rows: [ - { - sqft_spheroid: 928.6844047556697, - sqft_sphere: 926.609762750544, - sqm_spheroid: 86.27760440239217, - }, - ], - fields: [ - { - name: 'sqft_spheroid', - dataTypeID: 701, - }, - { - name: 'sqft_sphere', - dataTypeID: 701, - }, - { - name: 'sqm_spheroid', - dataTypeID: 701, - }, - ], - affectedRows: 0, - }, - ]) -}) - -it('ST_Polygonize', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, + expect(area3).toEqual([ + { + rows: [ + { + sqft_spheroid: 928.6844047556697, + sqft_sphere: 926.609762750544, + sqm_spheroid: 86.27760440239217, + }, + ], + fields: [ + { + name: 'sqft_spheroid', + dataTypeID: 701, + }, + { + name: 'sqft_sphere', + dataTypeID: 701, + }, + { + name: 'sqm_spheroid', + dataTypeID: 701, + }, + ], + affectedRows: 0, + }, + ]) }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - const res = await pg.exec(` + + it('ST_Polygonize', async () => { + const res = await pg.exec(` WITH data(geom) AS (VALUES ('LINESTRING (180 40, 30 20, 20 90)'::geometry) ,('LINESTRING (180 40, 160 160)'::geometry) @@ -200,34 +193,27 @@ it('ST_Polygonize', async () => { FROM data; `) - expect(res).toEqual([ - { - rows: [ - { - st_astext: - 'GEOMETRYCOLLECTION(POLYGON((180 40,30 20,20 90,70 70,80 130,160 160,180 40),(150 80,120 130,80 60,150 80)),POLYGON((80 60,120 130,150 80,80 60)),POLYGON((80 130,70 70,20 90,20 160,70 190,80 130)),POLYGON((160 160,80 130,70 190,160 160)))', - }, - ], - fields: [ - { - name: 'st_astext', - dataTypeID: 25, - }, - ], - affectedRows: 0, - }, - ]) -}) - -it('complex1', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, + expect(res).toEqual([ + { + rows: [ + { + st_astext: + 'GEOMETRYCOLLECTION(POLYGON((180 40,30 20,20 90,70 70,80 130,160 160,180 40),(150 80,120 130,80 60,150 80)),POLYGON((80 60,120 130,150 80,80 60)),POLYGON((80 130,70 70,20 90,20 160,70 190,80 130)),POLYGON((160 160,80 130,70 190,160 160)))', + }, + ], + fields: [ + { + name: 'st_astext', + dataTypeID: 25, + }, + ], + affectedRows: 0, + }, + ]) }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - await pg.exec(` + it('complex1', async () => { + await pg.exec(` -- Create test schema -- CREATE SCHEMA IF NOT EXISTS postgis_test; -- SET search_path TO postgis_test; @@ -240,7 +226,7 @@ it('complex1', async () => { geom GEOMETRY(Point, 4326) );`) - await pg.exec(` + await pg.exec(` CREATE TABLE rivers ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, @@ -281,20 +267,15 @@ it('complex1', async () => { ORDER BY distance_km; `) -}) - -it('The coordinates in GeoJSON are not sufficiently nested', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - debug: 1, }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - await expect( - pg.exec( - `SELECT '#3583', ST_AsText(ST_GeomFromGeoJSON('{"type":"MultiPolygon", "coordinates":[[[139.10030364990232,35.16777444430609],5842.4224490305424]]}'));`, - ), - ).rejects.toThrow(`The 'coordinates' in GeoJSON are not sufficiently nested`) + it('The coordinates in GeoJSON are not sufficiently nested', async () => { + await expect( + pg.exec( + `SELECT '#3583', ST_AsText(ST_GeomFromGeoJSON('{"type":"MultiPolygon", "coordinates":[[[139.10030364990232,35.16777444430609],5842.4224490305424]]}'));`, + ), + ).rejects.toThrow( + `The 'coordinates' in GeoJSON are not sufficiently nested`, + ) + }) }) From 368e1ffb0d16b4b1e6444c4ac33feed941c03a6a Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:45:16 +0200 Subject: [PATCH 14/18] same as above for notify --- packages/pglite/tests/notify.test.ts | 42 ++++++----- packages/pglite/tests/pg_textsearch.test.ts | 84 ++++++--------------- 2 files changed, 48 insertions(+), 78 deletions(-) diff --git a/packages/pglite/tests/notify.test.ts b/packages/pglite/tests/notify.test.ts index 001b947d7..29b9260a7 100644 --- a/packages/pglite/tests/notify.test.ts +++ b/packages/pglite/tests/notify.test.ts @@ -1,51 +1,59 @@ -import { describe, it, expect, vi } from 'vitest' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { PGlite } from '../dist/index.js' import { expectToThrowAsync } from './test-utils.js' describe('notify API', () => { - it('notify', async () => { - const db = new PGlite() + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create() + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create() + } + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) - await db.listen('test', (payload) => { + it('notify', async () => { + await pg.listen('test', (payload) => { expect(payload).toBe('321') }) - await db.exec("NOTIFY test, '321'") + await pg.exec("NOTIFY test, '321'") await new Promise((resolve) => setTimeout(resolve, 1000)) }) it('unlisten', async () => { - const db = new PGlite() - - const unsub = await db.listen('test', () => { + const unsub = await pg.listen('test', () => { throw new Error('Notification received after unsubscribed') }) await unsub() - await db.exec('NOTIFY test') + await pg.exec('NOTIFY test') await new Promise((resolve) => setTimeout(resolve, 1000)) }) it('onNotification', async () => { - const db = new PGlite() - - db.onNotification((chan, payload) => { + pg.onNotification((chan, payload) => { expect(chan).toBe('test') expect(payload).toBe('123') }) - await db.exec('LISTEN test') - await db.exec("NOTIFY test, '123'") + await pg.exec('LISTEN test') + await pg.exec("NOTIFY test, '123'") await new Promise((resolve) => setTimeout(resolve, 1000)) }) it('check notify case sensitivity + special chars as Postgresql', async () => { - const pg = new PGlite() - const allLower1 = vi.fn() await pg.listen('postgresdefaultlower', allLower1) await pg.exec(`NOTIFY postgresdefaultlower, 'payload1'`) @@ -111,8 +119,6 @@ describe('notify API', () => { }) it('check unlisten case sensitivity + special chars as Postgresql', async () => { - const pg = new PGlite() - const allLower1 = vi.fn() { const unsub1 = await pg.listen('postgresdefaultlower', allLower1) diff --git a/packages/pglite/tests/pg_textsearch.test.ts b/packages/pglite/tests/pg_textsearch.test.ts index 33ea75758..e9bbde5d3 100644 --- a/packages/pglite/tests/pg_textsearch.test.ts +++ b/packages/pglite/tests/pg_textsearch.test.ts @@ -2,8 +2,9 @@ * Tests for pg_textsearch extension. * Based on tests from https://github.com/timescale/pg_textsearch/tree/main/test/sql */ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -21,16 +22,30 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pg_textsearch/index.js')) describe(`pg_textsearch`, () => { - // From test/sql/basic.sql - it('extension creation and bm25 access method', async () => { - const pg = await PGlite.create({ - extensions: { - pg_textsearch, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pg_textsearch }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pg_textsearch }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + // From test/sql/basic.sql + it('extension creation and bm25 access method', async () => { // Test bm25 access method exists const res = await pg.query<{ amname: string }>( "SELECT amname FROM pg_am WHERE amname = 'bm25';", @@ -41,14 +56,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/basic.sql - bm25vector type it('bm25vector type exists and works', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') - // Test bm25vector type exists const res = await pg.query<{ pg_typeof: string }>( "SELECT pg_typeof('my_index:{database:2,system:1}'::bm25vector);", @@ -64,14 +71,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/basic.sql - bm25query type it('bm25query type exists and works', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') - // Test bm25query type exists const res = await pg.query<{ pg_typeof: string }>( "SELECT pg_typeof('search terms'::bm25query);", @@ -87,13 +86,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/basic.sql - index creation and basic search it('bm25 index creation and basic search', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE test_docs (id SERIAL PRIMARY KEY, content TEXT); `) @@ -135,13 +127,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/queries.sql - realistic search queries it('top-k query patterns', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE articles ( id SERIAL PRIMARY KEY, @@ -199,13 +184,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/scoring1.sql - bulk vs incremental index build it('bulk build mode (insert then create index)', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE scoring_bulk ( id SERIAL PRIMARY KEY, @@ -246,13 +224,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/strings.sql - various text patterns it('handles various text patterns', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE text_patterns ( id SERIAL PRIMARY KEY, @@ -289,13 +260,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/updates.sql - update and delete operations it('handles updates and deletes', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE update_test ( id SERIAL PRIMARY KEY, From 1c1c4ac44cc0a4e5efb1d94b7fafbbfcdb1b7b93 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:47:47 +0200 Subject: [PATCH 15/18] same as above for pg_dump tests --- packages/pglite-tools/tests/pg_dump.test.ts | 29 ++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/pglite-tools/tests/pg_dump.test.ts b/packages/pglite-tools/tests/pg_dump.test.ts index e25a56e8a..21c3db7b5 100644 --- a/packages/pglite-tools/tests/pg_dump.test.ts +++ b/packages/pglite-tools/tests/pg_dump.test.ts @@ -1,11 +1,26 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '@electric-sql/pglite' import { pgDump } from '../dist/pg_dump.js' import * as fs from 'fs/promises' describe('pgDump', () => { + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create() + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create() + } + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + it('should dump an empty database', async () => { - const pg = await PGlite.create() const dump = await pgDump({ pg }) expect(dump).toBeInstanceOf(File) @@ -16,8 +31,6 @@ describe('pgDump', () => { }) it('should dump an empty database multiple times', async () => { - const pg = await PGlite.create() - for (let i = 0; i < 5; i++) { const fileName = `dump_${i}.sql` const dump = await pgDump({ pg, fileName }) @@ -31,8 +44,6 @@ describe('pgDump', () => { }) it('should dump a database with tables and data', async () => { - const pg = await PGlite.create() - // Create test tables and insert data await pg.exec(` CREATE TABLE test1 ( @@ -63,14 +74,12 @@ describe('pgDump', () => { }) it('should respect custom filename', async () => { - const pg = await PGlite.create() const dump = await pgDump({ pg, fileName: 'custom.sql' }) expect(dump.name).toBe('custom.sql') }) it('should handle custom pg_dump arguments', async () => { - const pg = await PGlite.create() await pg.exec(` CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT); INSERT INTO test (name) VALUES ('row1'); @@ -118,8 +127,6 @@ describe('pgDump', () => { }) it('pg_dump should not change SEARCH_PATH', async () => { - const pg = await PGlite.create() - await pg.exec(`SET SEARCH_PATH = amigo;`) const initialSearchPath = await pg.query('SHOW SEARCH_PATH;') @@ -168,8 +175,6 @@ describe('pgDump', () => { }) it('param --quote-all-identifiers should work', async () => { - const pg = await PGlite.create() - // Create test tables and insert data await pg.exec(` CREATE TABLE test1 ( From fd7b0896f01999bc92b471bda7e433b4f2788395 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:48:43 +0200 Subject: [PATCH 16/18] same as above for pg_dump tests take 2 --- packages/pglite-tools/tests/pg_dump.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/pglite-tools/tests/pg_dump.test.ts b/packages/pglite-tools/tests/pg_dump.test.ts index 21c3db7b5..41240c243 100644 --- a/packages/pglite-tools/tests/pg_dump.test.ts +++ b/packages/pglite-tools/tests/pg_dump.test.ts @@ -94,16 +94,14 @@ describe('pgDump', () => { }) it('should be able to restore dumped database', async () => { - const pg1 = await PGlite.create() - // Create original database - await pg1.exec(` + await pg.exec(` CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT); INSERT INTO test (name) VALUES ('row1'), ('row2'); `) const initialSearchPath = ( - await pg1.query<{ search_path: string }>('SHOW SEARCH_PATH;') + await pg.query<{ search_path: string }>('SHOW SEARCH_PATH;') ).rows[0].search_path // Dump database From 44f4b7ca7a07cf03fe2088b6a96637a6930d06dd Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 08:53:59 +0200 Subject: [PATCH 17/18] same as above for describe query --- packages/pglite/tests/describe-query.test.ts | 36 +++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/pglite/tests/describe-query.test.ts b/packages/pglite/tests/describe-query.test.ts index 1a28103b3..c4336dce6 100644 --- a/packages/pglite/tests/describe-query.test.ts +++ b/packages/pglite/tests/describe-query.test.ts @@ -1,9 +1,24 @@ -import { test, expect } from 'vitest' +import { test, expect, afterEach, beforeEach } from 'vitest' import { PGlite } from '../dist/index.js' +let pg: PGlite +let dataDirArchive: File | Blob +beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create() + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create() + } +}) +afterEach(async () => { + if (!pg.closed) { + await pg.close() + } +}) + test('describeQuery returns parameter and result types', async () => { - const db = await PGlite.create() - await db.query(` + await pg.query(` CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, @@ -12,7 +27,7 @@ test('describeQuery returns parameter and result types', async () => { ) `) - const description = await db.describeQuery( + const description = await pg.describeQuery( 'SELECT name, age FROM users WHERE id = $1 AND active = $2', ) @@ -34,9 +49,7 @@ test('describeQuery returns parameter and result types', async () => { }) test('describeQuery handles queries with no parameters or results', async () => { - const db = await PGlite.create() - - const description = await db.describeQuery('SELECT 1') + const description = await pg.describeQuery('SELECT 1') expect(description.queryParams).toHaveLength(0) expect(description.resultFields).toHaveLength(1) @@ -44,15 +57,14 @@ test('describeQuery handles queries with no parameters or results', async () => }) test('describeQuery handles INSERT queries', async () => { - const db = await PGlite.create() - await db.query(` + await pg.query(` CREATE TABLE test ( id INTEGER PRIMARY KEY, value TEXT ) `) - const description = await db.describeQuery( + const description = await pg.describeQuery( 'INSERT INTO test (id, value) VALUES ($1, $2)', ) @@ -63,9 +75,7 @@ test('describeQuery handles INSERT queries', async () => { }) test('describeQuery handles invalid queries', async () => { - const db = await PGlite.create() - await expect( - db.describeQuery('SELECT * FROM nonexistent_table'), + pg.describeQuery('SELECT * FROM nonexistent_table'), ).rejects.toThrow(/relation "nonexistent_table" does not exist/) }) From 009ed15deb1ceef0150b99dea02b5e01a172ca21 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 09:04:42 +0200 Subject: [PATCH 18/18] bug fix --- packages/pglite-tools/tests/pg_dump.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pglite-tools/tests/pg_dump.test.ts b/packages/pglite-tools/tests/pg_dump.test.ts index 41240c243..0174a48fb 100644 --- a/packages/pglite-tools/tests/pg_dump.test.ts +++ b/packages/pglite-tools/tests/pg_dump.test.ts @@ -105,7 +105,7 @@ describe('pgDump', () => { ).rows[0].search_path // Dump database - const dump = await pgDump({ pg: pg1 }) + const dump = await pgDump({ pg }) const dumpContent = await dump.text() // Create new database and restore