diff --git a/cibuild.sh b/cibuild.sh index 47103745..c7ca3066 100755 --- a/cibuild.sh +++ b/cibuild.sh @@ -181,7 +181,6 @@ then # TEMP FIX for SDK SSL_INCDIR=$EMSDK/upstream/emscripten/cache/sysroot/include/openssl [ -f $SSL_INCDIR/evp.h ] || ln -s $PREFIX/include/openssl $SSL_INCDIR - SKIP="\ [\ sslinfo bool_plperl hstore_plperl hstore_plpython jsonb_plperl jsonb_plpython\ diff --git a/packages/benchmark/benchmarks-rtt.js b/packages/benchmark/benchmarks-rtt.js index 40d0eb0f..bf0bcfb2 100644 --- a/packages/benchmark/benchmarks-rtt.js +++ b/packages/benchmark/benchmarks-rtt.js @@ -15,6 +15,23 @@ const CONFIGURATIONS = new Map( db: "pglite", dataDir: "idb://benchmark-rtt", }, + { + label: "PGlite IDB
relaxed durability", + db: "pglite", + dataDir: "idb://benchmark-rtt-rd", + options: { relaxedDurability: true }, + }, + { + label: "PGlite OPFS AHP", + db: "pglite", + dataDir: "opfs-ahp://benchmark-rtt", + }, + { + label: "PGlite OPFS AHP
relaxed durability", + db: "pglite", + dataDir: "opfs-ahp://benchmark-rtt-rd", + options: { relaxedDurability: true }, + }, { label: 'SQLite Memory', db: 'wa-sqlite', @@ -39,6 +56,14 @@ const CONFIGURATIONS = new Map( vfsClass: 'OriginPrivateFileSystemVFS', vfsArgs: [] }, + { + label: 'SQLite OPFS AHP', + db: 'wa-sqlite', + isAsync: false, + vfsModule: './node_modules/wa-sqlite/src/examples/AccessHandlePoolVFS.js', + vfsClass: 'AccessHandlePoolVFS', + vfsArgs: ['/benchmark-rtt-sqlite-ahp'] + }, ].map((obj) => [obj.label, obj]) ); @@ -107,7 +132,11 @@ document.getElementById("start").addEventListener("click", async (event) => { // Remove OPFS const root = await navigator.storage.getDirectory(); for await (const handle of root.values()) { - await root.removeEntry(handle.name, { recursive: true }); + try { + await root.removeEntry(handle.name, { recursive: true }); + } catch (e) { + // ignore + } } const Comlink = await ComlinkReady; @@ -170,6 +199,6 @@ document.getElementById("start").addEventListener("click", async (event) => { function addEntry(parent, text) { const tag = parent.parentElement.tagName === "TBODY" ? "td" : "th"; const child = document.createElement(tag); - child.textContent = text; + child.innerHTML = text; parent.appendChild(child); } diff --git a/packages/benchmark/benchmarks.js b/packages/benchmark/benchmarks.js index c4889111..96673f30 100644 --- a/packages/benchmark/benchmarks.js +++ b/packages/benchmark/benchmarks.js @@ -18,6 +18,24 @@ const CONFIGURATIONS = new Map( label: "Emscripten IndexedDB FS", dataDir: "idb://benchmark", }, + { + label: "Emscripten IndexedDB FS
relaxed durability", + dataDir: "idb://benchmark-rd", + options: { relaxedDurability: true }, + }, + { + label: "OPFS Access Handle Pool", + dataDir: "opfs-ahp://benchmark-ahp", + }, + { + label: "OPFS Access Handle Pool
relaxed durability", + dataDir: "opfs-ahp://benchmark-ahp-rd", + options: { relaxedDurability: true }, + }, + // { + // label: "OPFS Worker", + // dataDir: "opfs-worker://benchmark-worker", + // }, ].map((obj) => [obj.label, obj]) ); @@ -77,6 +95,16 @@ document.getElementById("start").addEventListener("click", async (event) => { } }); + // Remove OPFS + const root = await navigator.storage.getDirectory(); + for await (const handle of root.values()) { + try { + await root.removeEntry(handle.name, { recursive: true }); + } catch (e) { + // ignore + } + } + const benchmarks = await benchmarksReady; const Comlink = await ComlinkReady; try { @@ -101,6 +129,7 @@ document.getElementById("start").addEventListener("click", async (event) => { const query = await workerProxy({ dataDir: config.dataDir, label: config.label, + options: config.options, }); await query(preamble); @@ -123,6 +152,7 @@ document.getElementById("start").addEventListener("click", async (event) => { document.getElementById("error").textContent = e.stack.includes(e.message) ? e.stack : `${e.stack}\n${e.message}`; + throw e; } finally { // @ts-ignore event.target.disabled = false; @@ -132,6 +162,6 @@ document.getElementById("start").addEventListener("click", async (event) => { function addEntry(parent, text) { const tag = parent.parentElement.tagName === "TBODY" ? "td" : "th"; const child = document.createElement(tag); - child.textContent = text; + child.innerHTML = text; parent.appendChild(child); } diff --git a/packages/benchmark/demo-worker.js b/packages/benchmark/demo-worker.js index 3ecac0e5..1df1e830 100644 --- a/packages/benchmark/demo-worker.js +++ b/packages/benchmark/demo-worker.js @@ -14,7 +14,20 @@ import { PGlite } from "../pglite/dist/index.js"; * @returns {Promise} */ async function open(config) { - const pg = new PGlite(config.dataDir); + if (config.dataDir.startsWith("opfs-")) { + // delete the existing database + const root = await navigator.storage.getDirectory(); + const dirName = config.dataDir.slice(config.dataDir.indexOf("://") + 3); + try { + const dir = await root.getDirectoryHandle(dirName, { create: false }); + await dir.remove(); + } catch (e) { + // ignore + } + } + + console.log("Opening PGLite database:", JSON.stringify(config, null, 2)); + const pg = new PGlite(config.dataDir, config.options); await pg.waitReady; // Create the query interface. diff --git a/packages/benchmark/package.json b/packages/benchmark/package.json index bcfbe9cc..99ab9c34 100644 --- a/packages/benchmark/package.json +++ b/packages/benchmark/package.json @@ -18,6 +18,6 @@ "tsx": "^4.7.1" }, "dependencies": { - "wa-sqlite": "github:rhashimoto/wa-sqlite" + "wa-sqlite": "github:rhashimoto/wa-sqlite#v0.9.14" } } diff --git a/packages/benchmark/rtt-demo-worker.js b/packages/benchmark/rtt-demo-worker.js index 4deb7875..c7d7af38 100644 --- a/packages/benchmark/rtt-demo-worker.js +++ b/packages/benchmark/rtt-demo-worker.js @@ -4,7 +4,7 @@ import * as SQLite from './node_modules/wa-sqlite/src/sqlite-api.js'; import { createTag } from "./node_modules/wa-sqlite/src/examples/tag.js"; -import { PGlite } from "../pglite/dist/index.js"; +import { PGlite } from "../pglite/dist/index.js?1"; const WA_SQLITE = './node_modules/wa-sqlite/dist/wa-sqlite.mjs'; const WA_SQLITE_ASYNC = './node_modules/wa-sqlite/dist/wa-sqlite-async.mjs'; @@ -15,18 +15,17 @@ const WA_SQLITE_ASYNC = './node_modules/wa-sqlite/dist/wa-sqlite-async.mjs'; ); async function open(config) { - console.log('Opening database:', config) if (config.db === 'wa-sqlite') { - console.log('Opening SQLite database:', config) + console.log('Opening SQLite database:', JSON.stringify(config, null, 2)) return openSQLite(config); } else if (config.db === 'pglite') { - console.log('Opening PGLite database:', config) + console.log('Opening PGLite database:', JSON.stringify(config, null, 2)) return openPGlite(config); } } async function openPGlite(config) { - const pg = new PGlite(config.dataDir); + const pg = new PGlite(config.dataDir, config.options); await pg.waitReady; // Create the query interface. diff --git a/packages/pglite/README.md b/packages/pglite/README.md index 9074749f..6a2f3bb1 100644 --- a/packages/pglite/README.md +++ b/packages/pglite/README.md @@ -318,12 +318,33 @@ The `.query()` method can take a TypeScript type describing the expected shap ### Web Workers: -It's likely that you will want to run PGlite in a Web Worker so that it doesn't block the main thread. To aid in this we provide a `PGliteWorker` with the same API as the core `PGlite` but it runs Postgres in a dedicated Web Worker. To use, import from the `/worker` export: +It's likely that you will want to run PGlite in a Web Worker so that it doesn't block the main thread. To aid in this we provide a `PGliteWorker` with the same API as the core `PGlite` but it runs Postgres in a dedicated Web Worker. + +First you need to create a js file for your worker instance, initiate PGlite with the worker extension, and start it: + +```js +// my-pglite-worker.js +import { PGlite } from "@electric-sql/pglite"; +import { worker } from "@electric-sql/pglite/worker"; + +worker({ + async init() { + return new PGlite(); + }, +}); +``` + +Then connect the `PGliteWorker` to your new worker process: ```js import { PGliteWorker } from "@electric-sql/pglite/worker"; -const pg = new PGliteWorker('idb://my-database'); +const pg = new PGliteWorker( + new Worker(new URL("./my-pglite-worker.js", import.meta.url), { + type: "module", + }) +); + await pg.exec(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, diff --git a/packages/pglite/examples/opfs-worker.js b/packages/pglite/examples/opfs-worker.js new file mode 100644 index 00000000..9d03d3d3 --- /dev/null +++ b/packages/pglite/examples/opfs-worker.js @@ -0,0 +1,12 @@ +import { PGlite } from "../dist/index.js"; +import { worker } from "../dist/worker/index.js"; + +worker({ + async init() { + const pg = new PGlite("opfs-ahp://my-test-db2"); + // If you want run any specific setup code for the worker process, you can do it here. + return pg; + }, +}); + +console.log("Worker process started"); diff --git a/packages/pglite/examples/opfs.html b/packages/pglite/examples/opfs.html new file mode 100644 index 00000000..cf3cb6a1 --- /dev/null +++ b/packages/pglite/examples/opfs.html @@ -0,0 +1,49 @@ + diff --git a/packages/pglite/examples/worker-process.js b/packages/pglite/examples/worker-process.js new file mode 100644 index 00000000..ac51d9ab --- /dev/null +++ b/packages/pglite/examples/worker-process.js @@ -0,0 +1,17 @@ +import { PGlite } from "../dist/index.js"; +import { worker } from "../dist/worker/index.js"; +import { vector } from "../dist/vector/index.js"; + +worker({ + async init() { + const pg = new PGlite({ + extensions: { + vector, + }, + }); + // If you want run any specific setup code for the worker process, you can do it here. + return pg; + }, +}); + +console.log("Worker process started"); diff --git a/packages/pglite/examples/worker.html b/packages/pglite/examples/worker.html index 3769bfa1..f258c3b6 100644 --- a/packages/pglite/examples/worker.html +++ b/packages/pglite/examples/worker.html @@ -1,47 +1,84 @@ - + // pg.live.query( + pg.live.incrementalQuery( + `SELECT * FROM test`, + [], + 'id', + (data) => { + const output = document.getElementById("output"); + output.textContent = JSON.stringify(data.rows, null, 2); + } + ); + + + +

PGlite Worker Example

+

Leader: false

+

+ + +

+

+  
+
diff --git a/packages/pglite/package.json b/packages/pglite/package.json
index 663e6408..e04a9510 100644
--- a/packages/pglite/package.json
+++ b/packages/pglite/package.json
@@ -21,22 +21,38 @@
     ".": "./dist/index.js",
     "./live": "./dist/live/index.js",
     "./worker": "./dist/worker/index.js",
-    "./vector": "./dist/vector/index.js"
+    "./vector": "./dist/vector/index.js",
+    "./nodefs": "./dist/fs/nodefs.js",
+    "./opfs-ahp": "./dist/fs/opfs-ahp/index.js",
+    "./contrib/*": "./dist/contrib/*.js"
   },
   "typesVersions": {
     "*": {
+      "live": [
+        "./dist/live/index.d.ts"
+      ],
       "worker": [
         "./dist/worker/index.d.ts"
       ],
       "vector": [
         "./dist/vector/index.d.ts"
+      ],
+      "nodefs": [
+        "./dist/fs/nodefs.d.ts"
+      ],
+      "opfs-ahp": [
+        "./dist/fs/opfs-ahp/index.d.ts"
+      ],
+      "contrib/*": [
+        "./dist/contrib/*.d.ts"
       ]
     }
   },
   "type": "module",
   "types": "dist/index.d.ts",
   "files": [
-    "dist"
+    "dist",
+    "release"
   ],
   "repository": {
     "type": "git",
@@ -47,8 +63,10 @@
     "test": "rm -rf ./pgdata-test && concurrently -s first --hide 1 --prefix none -k \"sleep 2 && ava tests/*.test.js tests/**/*.test.js\" \"npx http-server --port 3334 ./\"",
     "test:quick": "rm -rf ./pgdata-bun-test && ava tests/*.test.js tests/target/node-*.test.js",
     "test:bun": "rm -rf ./pgdata-test && npx bun test tests/basic.test.js && npx bun test tests/pgvector.test.js && npx bun test tests/targets/node-fs.test.js",
-    "build:js": "tsup && tsx scripts/bundle-wasm.ts",
+    "build:js": "tsup",
     "build": "npm run build:js",
+    "dev": "concurrently \"tsup --watch\" \"sleep 1 && tsx scripts/bundle-wasm.ts\" \"pnpm dev-server\"",
+    "dev-server": "pnpm http-server ../",
     "format": "prettier --write ./src"
   },
   "devDependencies": {
@@ -59,14 +77,14 @@
     "ava": "^6.1.2",
     "buffer": "^6.0.3",
     "bun": "^1.1.18",
-    "comlink": "^4.4.1",
     "concurrently": "^8.2.2",
     "http-server": "^14.1.1",
     "pg-protocol": "^1.6.1",
     "playwright": "^1.42.1",
     "prettier": "3.2.5",
+    "serve": "^14.2.3",
     "tinytar": "^0.1.0",
-    "tsup": "^8.0.2",
+    "tsup": "^8.1.0",
     "tsx": "^4.7.1",
     "typescript": "^5.3.3"
   }
diff --git a/packages/pglite/src/contrib/adminpack.ts b/packages/pglite/src/contrib/adminpack.ts
new file mode 100644
index 00000000..9e1a0c49
--- /dev/null
+++ b/packages/pglite/src/contrib/adminpack.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/adminpack.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const adminpack = {
+  name: "adminpack",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/amcheck.ts b/packages/pglite/src/contrib/amcheck.ts
new file mode 100644
index 00000000..9fb6e12d
--- /dev/null
+++ b/packages/pglite/src/contrib/amcheck.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/amcheck.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const amcheck = {
+  name: "amcheck",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/auto_explain.ts b/packages/pglite/src/contrib/auto_explain.ts
new file mode 100644
index 00000000..9f1e6bca
--- /dev/null
+++ b/packages/pglite/src/contrib/auto_explain.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/auto_explain.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const auto_explain = {
+  name: "auto_explain",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/bloom.ts b/packages/pglite/src/contrib/bloom.ts
new file mode 100644
index 00000000..479281d5
--- /dev/null
+++ b/packages/pglite/src/contrib/bloom.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/bloom.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const bloom = {
+  name: "bloom",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/btree_gin.ts b/packages/pglite/src/contrib/btree_gin.ts
new file mode 100644
index 00000000..b32d4b60
--- /dev/null
+++ b/packages/pglite/src/contrib/btree_gin.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/btree_gin.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const btree_gin = {
+  name: "btree_gin",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/btree_gist.ts b/packages/pglite/src/contrib/btree_gist.ts
new file mode 100644
index 00000000..f9b4e0c7
--- /dev/null
+++ b/packages/pglite/src/contrib/btree_gist.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/btree_gist.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const btree_gist = {
+  name: "btree_gist",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/citext.ts b/packages/pglite/src/contrib/citext.ts
new file mode 100644
index 00000000..f9a7eb8c
--- /dev/null
+++ b/packages/pglite/src/contrib/citext.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/citext.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const citext = {
+  name: "citext",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/cube.ts b/packages/pglite/src/contrib/cube.ts
new file mode 100644
index 00000000..818b1323
--- /dev/null
+++ b/packages/pglite/src/contrib/cube.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/cube.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const cube = {
+  name: "cube",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/earthdistance.ts b/packages/pglite/src/contrib/earthdistance.ts
new file mode 100644
index 00000000..8408bdc8
--- /dev/null
+++ b/packages/pglite/src/contrib/earthdistance.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/earthdistance.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const earthdistance = {
+  name: "earthdistance",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/fuzzystrmatch.ts b/packages/pglite/src/contrib/fuzzystrmatch.ts
new file mode 100644
index 00000000..5a514ff3
--- /dev/null
+++ b/packages/pglite/src/contrib/fuzzystrmatch.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/fuzzystrmatch.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const fuzzystrmatch = {
+  name: "fuzzystrmatch",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/hstore.ts b/packages/pglite/src/contrib/hstore.ts
new file mode 100644
index 00000000..fff4fbcf
--- /dev/null
+++ b/packages/pglite/src/contrib/hstore.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/hstore.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const hstore = {
+  name: "hstore",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/isn.ts b/packages/pglite/src/contrib/isn.ts
new file mode 100644
index 00000000..122be5f5
--- /dev/null
+++ b/packages/pglite/src/contrib/isn.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/isn.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const isn = {
+  name: "isn",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/lo.ts b/packages/pglite/src/contrib/lo.ts
new file mode 100644
index 00000000..27c7a744
--- /dev/null
+++ b/packages/pglite/src/contrib/lo.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/lo.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const lo = {
+  name: "lo",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/ltree.ts b/packages/pglite/src/contrib/ltree.ts
new file mode 100644
index 00000000..b34650cc
--- /dev/null
+++ b/packages/pglite/src/contrib/ltree.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/ltree.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const ltree = {
+  name: "ltree",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/pg_trgm.ts b/packages/pglite/src/contrib/pg_trgm.ts
new file mode 100644
index 00000000..7ef179f4
--- /dev/null
+++ b/packages/pglite/src/contrib/pg_trgm.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/pg_trgm.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const pg_trgm = {
+  name: "pg_trgm",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/seg.ts b/packages/pglite/src/contrib/seg.ts
new file mode 100644
index 00000000..2f223d97
--- /dev/null
+++ b/packages/pglite/src/contrib/seg.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/seg.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const seg = {
+  name: "seg",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/tablefunc.ts b/packages/pglite/src/contrib/tablefunc.ts
new file mode 100644
index 00000000..792d0129
--- /dev/null
+++ b/packages/pglite/src/contrib/tablefunc.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/tablefunc.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const tablefunc = {
+  name: "tablefunc",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/tcn.ts b/packages/pglite/src/contrib/tcn.ts
new file mode 100644
index 00000000..36e81031
--- /dev/null
+++ b/packages/pglite/src/contrib/tcn.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/tcn.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const tcn = {
+  name: "tcn",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/tsm_system_rows.ts b/packages/pglite/src/contrib/tsm_system_rows.ts
new file mode 100644
index 00000000..251d4fe3
--- /dev/null
+++ b/packages/pglite/src/contrib/tsm_system_rows.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/tsm_system_rows.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const tsm_system_rows = {
+  name: "tsm_system_rows",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/contrib/tsm_system_time.ts b/packages/pglite/src/contrib/tsm_system_time.ts
new file mode 100644
index 00000000..8dda49f9
--- /dev/null
+++ b/packages/pglite/src/contrib/tsm_system_time.ts
@@ -0,0 +1,16 @@
+import type {
+  Extension,
+  ExtensionSetupResult,
+  PGliteInterface,
+} from "../interface";
+
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
+  return {
+    bundlePath: new URL("../../release/tsm_system_time.tar.gz", import.meta.url),
+  } satisfies ExtensionSetupResult;
+};
+
+export const tsm_system_time = {
+  name: "tsm_system_time",
+  setup,
+} satisfies Extension;
diff --git a/packages/pglite/src/extensionUtils.ts b/packages/pglite/src/extensionUtils.ts
index 02dcc071..3e7a3c49 100644
--- a/packages/pglite/src/extensionUtils.ts
+++ b/packages/pglite/src/extensionUtils.ts
@@ -35,7 +35,7 @@ export async function loadExtensionBundle(
     const response = await fetch(bundlePath.toString());
     if (!response.ok || !response.body) {
       return null;
-    } else if (response.headers.get('Content-Encoding') === 'gzip') {
+    } else if (response.headers.get("Content-Encoding") === "gzip") {
       // Although the bundle is manually compressed, some servers will recognize
       // that and add a content-encoding header. Fetch will then automatically
       // decompress the response.
diff --git a/packages/pglite/src/fs/idbfs.ts b/packages/pglite/src/fs/idbfs.ts
index 4965c92e..6b62510d 100644
--- a/packages/pglite/src/fs/idbfs.ts
+++ b/packages/pglite/src/fs/idbfs.ts
@@ -38,7 +38,7 @@ export class IdbFs extends FilesystemBase {
     });
   }
 
-  syncToFs(fs: FS) {
+  syncToFs(fs: FS, relaxedDurability?: boolean) {
     return new Promise((resolve, reject) => {
       fs.syncfs(false, (err: any) => {
         if (err) {
diff --git a/packages/pglite/src/fs/index.ts b/packages/pglite/src/fs/index.ts
index 0dfd5423..ebe8844a 100644
--- a/packages/pglite/src/fs/index.ts
+++ b/packages/pglite/src/fs/index.ts
@@ -20,6 +20,10 @@ export function parseDataDir(dataDir?: string) {
     // Remove the idb:// prefix, and use indexeddb filesystem
     dataDir = dataDir.slice(6);
     fsType = "idbfs";
+  } else if (dataDir?.startsWith("opfs-ahp://")) {
+    // Remove the opfsahp:// prefix, and use opfs access handle pool filesystem
+    dataDir = dataDir.slice(11);
+    fsType = "opfs-ahp";
   } else if (!dataDir || dataDir?.startsWith("memory://")) {
     // Use in-memory filesystem
     fsType = "memoryfs";
@@ -38,6 +42,10 @@ export async function loadFs(dataDir?: string, fsType?: FsType) {
     fs = new NodeFS(dataDir);
   } else if (dataDir && fsType === "idbfs") {
     fs = new IdbFs(dataDir);
+  } else if (dataDir && fsType === "opfs-ahp") {
+    // Lazy load the opfs-ahp to so that it's optional in the bundle
+    const { OpfsAhpFS } = await import("./opfs-ahp/index.js");
+    fs = new OpfsAhpFS(dataDir);
   } else {
     fs = new MemoryFS();
   }
diff --git a/packages/pglite/src/fs/opfs-ahp/emscriptenFs.ts b/packages/pglite/src/fs/opfs-ahp/emscriptenFs.ts
new file mode 100644
index 00000000..ab168b3d
--- /dev/null
+++ b/packages/pglite/src/fs/opfs-ahp/emscriptenFs.ts
@@ -0,0 +1,371 @@
+import type { PostgresMod } from "../../postgresMod.js";
+import type { OpfsAhp } from "./opfsAhp.js";
+import { FsError, ERRNO_CODES } from "./types.js";
+
+export type FileSystemType = Emscripten.FileSystemType & {
+  createNode: (
+    parent: FSNode | null,
+    name: string,
+    mode: number,
+    dev?: any,
+  ) => FSNode;
+  node_ops: FS.NodeOps;
+  stream_ops: FS.StreamOps & {
+    dup: (stream: FSStream) => void;
+    mmap: (
+      stream: FSStream,
+      length: number,
+      position: number,
+      prot: any,
+      flags: any,
+    ) => { ptr: number; allocated: boolean };
+    msync: (
+      stream: FSStream,
+      buffer: Uint8Array,
+      offset: number,
+      length: number,
+      mmapFlags: any,
+    ) => number;
+  };
+} & { [key: string]: any };
+
+type FSNode = FS.FSNode & {
+  node_ops: FS.NodeOps;
+  stream_ops: FS.StreamOps;
+};
+
+type FSStream = FS.FSStream & {
+  node: FSNode;
+  shared: {
+    refcount: number;
+  };
+};
+
+export interface OpfsMount extends FS.Mount {
+  opts: {
+    root: string;
+  };
+}
+
+type OpfsNode = FSNode & {};
+
+type EmscriptenFS = PostgresMod["FS"] & {
+  createNode: (
+    parent: FSNode | null,
+    name: string,
+    mode: number,
+    dev?: any,
+  ) => FSNode;
+};
+
+/**
+ * Create an emscripten filesystem that uses the AHP filesystem.
+ * @param Module The emscripten module
+ * @param opfsAhp The AHP filesystem - see `OpfsAhp.ts`
+ * @returns The emscripten filesystem
+ */
+export const createOPFSAHP = (Module: PostgresMod, opfsAhp: OpfsAhp) => {
+  const FS = Module.FS as EmscriptenFS;
+  const OPFS = {
+    tryFSOperation(f: () => T): T {
+      try {
+        return f();
+      } catch (e: any) {
+        if (!e.code) throw e;
+        if (e.code === "UNKNOWN") throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+        throw new FS.ErrnoError(e.code);
+      }
+    },
+    mount(mount: OpfsMount): FSNode {
+      return OPFS.createNode(null, "/", 16384 | 511, 0);
+    },
+    syncfs(
+      mount: FS.Mount,
+      populate: any, // This has the wrong type in @types/emscripten
+      done: (err?: number | null) => unknown,
+    ): void {
+      // noop
+    },
+    createNode(
+      parent: FSNode | null,
+      name: string,
+      mode: number,
+      dev?: any,
+    ): OpfsNode {
+      if (!FS.isDir(mode) && !FS.isFile(mode)) {
+        throw new FS.ErrnoError(28);
+      }
+      const node = FS.createNode(parent, name, mode);
+      node.node_ops = OPFS.node_ops;
+      node.stream_ops = OPFS.stream_ops;
+      return node;
+    },
+    getMode: function (path: string): number {
+      log("getMode", path);
+      return OPFS.tryFSOperation(() => {
+        const stats = opfsAhp.lstat(path);
+        return stats.mode;
+      });
+    },
+    realPath: function (node: FSNode): string {
+      const parts = [];
+      while (node.parent !== node) {
+        parts.push(node.name);
+        node = node.parent as FSNode;
+      }
+      parts.push((node.mount as OpfsMount).opts.root);
+      parts.reverse();
+      return parts.join("/");
+    },
+    node_ops: {
+      getattr(node: OpfsNode): FS.Stats {
+        log("getattr", OPFS.realPath(node));
+        const path = OPFS.realPath(node);
+        return OPFS.tryFSOperation(() => {
+          const stats = opfsAhp.lstat(path);
+          return {
+            ...stats,
+            dev: 0,
+            ino: node.id,
+            nlink: 1,
+            rdev: node.rdev,
+            atime: new Date(stats.atime),
+            mtime: new Date(stats.mtime),
+            ctime: new Date(stats.ctime),
+          };
+        });
+      },
+      setattr(node: OpfsNode, attr: FS.Stats): void {
+        log("setattr", OPFS.realPath(node), attr);
+        var path = OPFS.realPath(node);
+        OPFS.tryFSOperation(() => {
+          if (attr.mode !== undefined) {
+            opfsAhp.chmod(path, attr.mode);
+          }
+          if (attr.size !== undefined) {
+            opfsAhp.truncate(path, attr.size);
+          }
+          if (attr.timestamp !== undefined) {
+            opfsAhp.utimes(path, attr.timestamp, attr.timestamp);
+          }
+          if (attr.size !== undefined) {
+            opfsAhp.truncate(path, attr.size);
+          }
+        });
+      },
+      lookup(parent: FSNode, name: string): OpfsNode {
+        log("lookup", OPFS.realPath(parent), name);
+        const path = [OPFS.realPath(parent), name].join("/");
+        const mode = OPFS.getMode(path);
+        return OPFS.createNode(parent, name, mode);
+      },
+      mknod(
+        parent: FSNode,
+        name: string,
+        mode: number,
+        dev: unknown,
+      ): OpfsNode {
+        log("mknod", OPFS.realPath(parent), name, mode, dev);
+        const node = OPFS.createNode(parent, name, mode, dev);
+        // create the backing node for this in the fs root as well
+        const path = OPFS.realPath(node);
+        return OPFS.tryFSOperation(() => {
+          if (FS.isDir(node.mode)) {
+            opfsAhp.mkdir(path, { mode });
+          } else {
+            opfsAhp.writeFile(path, "", { mode });
+          }
+          return node;
+        });
+      },
+      rename(oldNode: OpfsNode, newDir: OpfsNode, newName: string): void {
+        log("rename", OPFS.realPath(oldNode), OPFS.realPath(newDir), newName);
+        const oldPath = OPFS.realPath(oldNode);
+        const newPath = [OPFS.realPath(newDir), newName].join("/");
+        OPFS.tryFSOperation(() => {
+          opfsAhp.rename(oldPath, newPath);
+        });
+        oldNode.name = newName;
+      },
+      unlink(parent: OpfsNode, name: string): void {
+        log("unlink", OPFS.realPath(parent), name);
+        const path = [OPFS.realPath(parent), name].join("/");
+        try {
+          opfsAhp.unlink(path);
+        } catch (e: any) {}
+      },
+      rmdir(parent: OpfsNode, name: string): void {
+        log("rmdir", OPFS.realPath(parent), name);
+        const path = [OPFS.realPath(parent), name].join("/");
+        return OPFS.tryFSOperation(() => {
+          opfsAhp.rmdir(path);
+        });
+      },
+      readdir(node: OpfsNode): string[] {
+        log("readdir", OPFS.realPath(node));
+        const path = OPFS.realPath(node);
+        return OPFS.tryFSOperation(() => {
+          return opfsAhp.readdir(path);
+        });
+      },
+      symlink(parent: FSNode, newName: string, oldPath: string): void {
+        log("symlink", OPFS.realPath(parent), newName, oldPath);
+        // This is not supported by OPFS
+        throw new FS.ErrnoError(63);
+      },
+      readlink(node: FSNode): string {
+        log("readlink", OPFS.realPath(node));
+        // This is not supported by OPFS
+        throw new FS.ErrnoError(63);
+      },
+    },
+    stream_ops: {
+      open(stream: FSStream): void {
+        log("open stream", OPFS.realPath(stream.node));
+        const path = OPFS.realPath(stream.node);
+        return OPFS.tryFSOperation(() => {
+          if (FS.isFile(stream.node.mode)) {
+            stream.shared.refcount = 1;
+            stream.nfd = opfsAhp.open(path);
+          }
+        });
+      },
+      close(stream: FSStream): void {
+        log("close stream", OPFS.realPath(stream.node));
+        return OPFS.tryFSOperation(() => {
+          if (
+            FS.isFile(stream.node.mode) &&
+            stream.nfd &&
+            --stream.shared.refcount === 0
+          ) {
+            opfsAhp.close(stream.nfd);
+          }
+        });
+      },
+      dup(stream: FSStream) {
+        log("dup stream", OPFS.realPath(stream.node));
+        stream.shared.refcount++;
+      },
+      read(
+        stream: FSStream, // Stream to read from
+        buffer: Uint8Array, // Buffer to read into - Wrong type in @types/emscripten
+        offset: number, // Offset in buffer to start writing to
+        length: number, // Number of bytes to read
+        position: number, // Position in file to read from
+      ): number {
+        log(
+          "read stream",
+          OPFS.realPath(stream.node),
+          offset,
+          length,
+          position,
+        );
+        if (length === 0) return 0;
+        const ret = OPFS.tryFSOperation(() =>
+          opfsAhp.read(
+            stream.nfd!,
+            buffer as unknown as Int8Array,
+            offset,
+            length,
+            position,
+          ),
+        );
+        return ret;
+      },
+      write(
+        stream: FSStream, // Stream to write to
+        buffer: Uint8Array, // Buffer to read from - Wrong type in @types/emscripten
+        offset: number, // Offset in buffer to start writing from
+        length: number, // Number of bytes to write
+        position: number, // Position in file to write to
+      ): number {
+        log(
+          "write stream",
+          OPFS.realPath(stream.node),
+          offset,
+          length,
+          position,
+        );
+        return OPFS.tryFSOperation(() =>
+          opfsAhp.write(
+            stream.nfd!,
+            buffer.buffer as unknown as Int8Array,
+            offset,
+            length,
+            position,
+          ),
+        );
+      },
+      llseek(stream: FSStream, offset: number, whence: number): number {
+        log("llseek stream", OPFS.realPath(stream.node), offset, whence);
+        var position = offset;
+        if (whence === 1) {
+          position += stream.position;
+        } else if (whence === 2) {
+          if (FS.isFile(stream.node.mode)) {
+            OPFS.tryFSOperation(() => {
+              var stat = opfsAhp.fstat(stream.nfd!);
+              position += stat.size;
+            });
+          }
+        }
+        if (position < 0) {
+          throw new FS.ErrnoError(28);
+        }
+        return position;
+      },
+      mmap(
+        stream: FSStream,
+        length: number,
+        position: number,
+        prot: any,
+        flags: any,
+      ) {
+        log(
+          "mmap stream",
+          OPFS.realPath(stream.node),
+          length,
+          position,
+          prot,
+          flags,
+        );
+        if (!FS.isFile(stream.node.mode)) {
+          throw new FS.ErrnoError(ERRNO_CODES.ENODEV);
+        }
+
+        var ptr = (Module as any).mmapAlloc(length); // TODO: Fix type and check this is exported
+
+        OPFS.stream_ops.read(
+          stream,
+          Module.HEAP8 as unknown as Uint8Array,
+          ptr,
+          length,
+          position,
+        );
+        return { ptr, allocated: true };
+      },
+      msync(
+        stream: FSStream,
+        buffer: Uint8Array,
+        offset: number,
+        length: number,
+        mmapFlags: any,
+      ) {
+        log(
+          "msync stream",
+          OPFS.realPath(stream.node),
+          offset,
+          length,
+          mmapFlags,
+        );
+        OPFS.stream_ops.write(stream, buffer, 0, length, offset);
+        return 0;
+      },
+    },
+  } satisfies FileSystemType;
+  return OPFS;
+};
+
+function log(...args: any[]) {
+  // console.log(...args);
+}
diff --git a/packages/pglite/src/fs/opfs-ahp/index.ts b/packages/pglite/src/fs/opfs-ahp/index.ts
new file mode 100644
index 00000000..a4dee6c3
--- /dev/null
+++ b/packages/pglite/src/fs/opfs-ahp/index.ts
@@ -0,0 +1,67 @@
+import { FilesystemBase } from "../types.js";
+import { PGDATA } from "../index.js";
+import type { PostgresMod, FS } from "../../postgresMod.js";
+import { createOPFSAHP } from "./emscriptenFs.js";
+import { OpfsAhp } from "./opfsAhp.js";
+import { dumpTar } from "../tarUtils.js";
+
+export interface OpfsAhpFSOptions {
+  initialPoolSize?: number;
+  maintainedPoolSize?: number;
+}
+
+/**
+ * PGlite OPFS access handle pool filesystem.
+ * Opens a pool of sync access handles and then allocates them as needed.
+ */
+export class OpfsAhpFS extends FilesystemBase {
+  #initialPoolSize: number;
+  #maintainedPoolSize: number;
+  opfsAhp?: OpfsAhp;
+
+  constructor(
+    dataDir: string,
+    { initialPoolSize, maintainedPoolSize }: OpfsAhpFSOptions = {},
+  ) {
+    super(dataDir);
+    this.#initialPoolSize = initialPoolSize ?? 1000;
+    this.#maintainedPoolSize = maintainedPoolSize ?? 100;
+  }
+
+  async emscriptenOpts(opts: Partial) {
+    this.opfsAhp = await OpfsAhp.create({
+      root: this.dataDir!,
+      initialPoolSize: this.#initialPoolSize,
+      maintainedPoolSize: this.#maintainedPoolSize,
+    });
+    const options: Partial = {
+      ...opts,
+      preRun: [
+        ...(opts.preRun || []),
+        (mod: PostgresMod) => {
+          const OPFS = createOPFSAHP(mod, this.opfsAhp!);
+          mod.FS.mkdir(PGDATA);
+          mod.FS.mount(OPFS, {}, PGDATA);
+        },
+      ],
+    };
+    return options;
+  }
+
+  async syncToFs(fs: FS, relaxedDurability = false) {
+    await this.opfsAhp?.maybeCheckpointState();
+    await this.opfsAhp?.maintainPool();
+    // console.log("syncToFs", relaxedDurability);
+    if (!relaxedDurability) {
+      this.opfsAhp?.flush();
+    }
+  }
+
+  async dumpTar(mod: FS, dbname: string) {
+    return dumpTar(mod, dbname);
+  }
+
+  async close(): Promise {
+    this.opfsAhp?.exit();
+  }
+}
diff --git a/packages/pglite/src/fs/opfs-ahp/opfsAhp.ts b/packages/pglite/src/fs/opfs-ahp/opfsAhp.ts
new file mode 100644
index 00000000..2af10555
--- /dev/null
+++ b/packages/pglite/src/fs/opfs-ahp/opfsAhp.ts
@@ -0,0 +1,670 @@
+import { FsError } from "./types.js";
+import type {
+  FsStats,
+  State,
+  FileSystemSyncAccessHandle,
+  Node,
+  FileNode,
+  DirectoryNode,
+  WALEntry,
+} from "./types.js";
+
+const STATE_FILE = "state.txt";
+const DATA_DIR = "data";
+const INITIAL_MODE = {
+  DIR: 16384,
+  FILE: 32768,
+};
+
+export interface OpfsAhpOptions {
+  root: string;
+  initialPoolSize?: number;
+  maintainedPoolSize?: number;
+}
+
+/**
+ * An OPFS Access Handle Pool VFS that exports a Node.js-like FS interface.
+ * This FS is then wrapped by an Emscripten FS interface in emscriptenFs.ts.
+ */
+export class OpfsAhp {
+  readyPromise: Promise;
+  #ready = false;
+
+  readonly root: string;
+  readonly initialPoolSize: number;
+  readonly maintainedPoolSize: number;
+
+  #opfsRootAh!: FileSystemDirectoryHandle;
+  #rootAh!: FileSystemDirectoryHandle;
+  #dataDirAh!: FileSystemDirectoryHandle;
+
+  #stateFH!: FileSystemFileHandle;
+  #stateSH!: FileSystemSyncAccessHandle;
+
+  #fh: Map = new Map();
+  #sh: Map = new Map();
+
+  #handleIdCounter = 0;
+  #openHandlePaths: Map = new Map();
+  #openHandleIds: Map = new Map();
+
+  state!: State;
+  lastCheckpoint = 0;
+  checkpointInterval = 1000 * 60; // 1 minute
+  poolCounter = 0;
+
+  #unsyncedSH = new Set();
+
+  constructor({ root, initialPoolSize, maintainedPoolSize }: OpfsAhpOptions) {
+    this.root = root;
+    this.initialPoolSize = initialPoolSize || 1000;
+    this.maintainedPoolSize = maintainedPoolSize || 100;
+    this.readyPromise = this.#init();
+  }
+
+  static async create(options: OpfsAhpOptions) {
+    const instance = new OpfsAhp(options);
+    await instance.readyPromise;
+    return instance;
+  }
+
+  async #init() {
+    this.#opfsRootAh = await navigator.storage.getDirectory();
+    this.#rootAh = await this.#resolveOpfsDirectory(this.root, {
+      create: true,
+    });
+    this.#dataDirAh = await this.#resolveOpfsDirectory(DATA_DIR, {
+      from: this.#rootAh,
+      create: true,
+    });
+
+    this.#stateFH = await this.#rootAh.getFileHandle(STATE_FILE, {
+      create: true,
+    });
+    this.#stateSH = await (this.#stateFH as any).createSyncAccessHandle();
+
+    const stateAB = new ArrayBuffer(this.#stateSH.getSize());
+    this.#stateSH.read(stateAB, { at: 0 });
+    let state: State;
+    const stateLines = new TextDecoder().decode(stateAB).split("\n");
+    // Line 1 is a base state object.
+    // Lines 1+n are WAL entries.
+
+    let isNewState = false;
+    try {
+      state = JSON.parse(stateLines[0]);
+    } catch (e) {
+      state = {
+        root: {
+          type: "directory",
+          lastModified: Date.now(),
+          mode: INITIAL_MODE.DIR,
+          children: {},
+        },
+        pool: [],
+      };
+      // write new state to file
+      this.#stateSH.truncate(0);
+      this.#stateSH.write(new TextEncoder().encode(JSON.stringify(state)), {
+        at: 0,
+      });
+      isNewState = true;
+    }
+    this.state = state;
+
+    // Apply WAL entries
+    const wal = stateLines
+      .slice(1)
+      .filter(Boolean)
+      .map((line) => JSON.parse(line));
+    for (const entry of wal) {
+      const methodName = `_${entry.opp}State`;
+      if (typeof this[methodName as keyof this] === "function") {
+        try {
+          (this[methodName as keyof this] as any)(...entry.args);
+        } catch (e) {
+          console.warn("Error applying OPFS AHP WAL entry", entry, e);
+        }
+      }
+    }
+
+    // Open all file handles for dir tree
+    const walkPromises: Promise[] = [];
+    const walk = async (node: Node) => {
+      if (node.type === "file") {
+        try {
+          const fh = await this.#dataDirAh.getFileHandle(node.backingFilename);
+          const sh: FileSystemSyncAccessHandle = await (
+            fh as any
+          ).createSyncAccessHandle();
+          this.#fh.set(node.backingFilename, fh);
+
+          this.#sh.set(node.backingFilename, sh);
+        } catch (e) {
+          console.error("Error opening file handle for node", node, e);
+        }
+      } else {
+        for (const child of Object.values(node.children)) {
+          walkPromises.push(walk(child));
+        }
+      }
+    };
+    await walk(this.state.root);
+
+    // Open all pool file handles
+    const poolPromises: Promise[] = [];
+    for (const filename of this.state.pool) {
+      poolPromises.push(
+        new Promise(async (resolve) => {
+          if (this.#fh.has(filename)) {
+            console.warn("File handle already exists for pool file", filename);
+          }
+          const fh = await this.#dataDirAh.getFileHandle(filename);
+          const sh: FileSystemSyncAccessHandle = await (
+            fh as any
+          ).createSyncAccessHandle();
+          this.#fh.set(filename, fh);
+          this.#sh.set(filename, sh);
+          resolve();
+        }),
+      );
+    }
+
+    await Promise.all([...walkPromises, ...poolPromises]);
+
+    await this.maintainPool(
+      isNewState ? this.initialPoolSize : this.maintainedPoolSize,
+    );
+
+    this.#ready = true;
+  }
+
+  get ready() {
+    return this.#ready;
+  }
+
+  async maintainPool(size?: number) {
+    size = size || this.maintainedPoolSize;
+    const change = size - this.state.pool.length;
+    const promises: Promise[] = [];
+    for (let i = 0; i < change; i++) {
+      promises.push(
+        new Promise(async (resolve) => {
+          ++this.poolCounter;
+          const filename = `${(Date.now() - 1704063600).toString(16).padStart(8, "0")}-${this.poolCounter.toString(16).padStart(8, "0")}`;
+          const fh = await this.#dataDirAh.getFileHandle(filename, {
+            create: true,
+          });
+          const sh: FileSystemSyncAccessHandle = await (
+            fh as any
+          ).createSyncAccessHandle();
+          this.#fh.set(filename, fh);
+          this.#sh.set(filename, sh);
+          this.#logWAL({
+            opp: "createPoolFile",
+            args: [filename],
+          });
+          this.state.pool.push(filename);
+          resolve();
+        }),
+      );
+    }
+    for (let i = 0; i > change; i--) {
+      promises.push(
+        new Promise(async (resolve) => {
+          const filename = this.state.pool.pop()!;
+          this.#logWAL({
+            opp: "deletePoolFile",
+            args: [filename],
+          });
+          const fh = this.#fh.get(filename)!;
+          const sh = this.#sh.get(filename);
+          sh?.close();
+          // @ts-ignore
+          await fh.remove();
+          this.#fh.delete(filename);
+          this.#sh.delete(filename);
+          resolve();
+        }),
+      );
+    }
+    await Promise.all(promises);
+  }
+
+  _createPoolFileState(filename: string) {
+    this.state.pool.push(filename);
+  }
+
+  _deletePoolFileState(filename: string) {
+    const index = this.state.pool.indexOf(filename);
+    if (index > -1) {
+      this.state.pool.splice(index, 1);
+    }
+  }
+
+  async maybeCheckpointState() {
+    if (Date.now() - this.lastCheckpoint > this.checkpointInterval) {
+      await this.checkpointState();
+    }
+  }
+
+  async checkpointState() {
+    const stateAB = new TextEncoder().encode(JSON.stringify(this.state));
+    this.#stateSH.truncate(0);
+    this.#stateSH.write(stateAB, { at: 0 });
+    this.#stateSH.flush();
+    this.lastCheckpoint = Date.now();
+  }
+
+  flush() {
+    for (const sh of this.#unsyncedSH) {
+      try {
+        sh.flush();
+      } catch (e) {} // The file may have been closed if it was deleted
+    }
+    this.#unsyncedSH.clear();
+  }
+
+  exit(): void {
+    for (const sh of this.#sh.values()) {
+      sh.close();
+    }
+    this.#stateSH.flush();
+    this.#stateSH.close();
+  }
+
+  // Filesystem API:
+
+  chmod(path: string, mode: number): void {
+    this.#tryWithWAL({ opp: "chmod", args: [path, mode] }, () => {
+      this._chmodState(path, mode);
+    });
+  }
+
+  _chmodState(path: string, mode: number): void {
+    const node = this.#resolvePath(path);
+    node.mode = mode;
+  }
+
+  close(fd: number): void {
+    const path = this.#getPathFromFd(fd);
+    this.#openHandlePaths.delete(fd);
+    this.#openHandleIds.delete(path);
+  }
+
+  fstat(fd: number): FsStats {
+    const path = this.#getPathFromFd(fd);
+    return this.lstat(path);
+  }
+
+  lstat(path: string): FsStats {
+    const node = this.#resolvePath(path);
+    const size =
+      node.type === "file" ? this.#sh.get(node.backingFilename)!.getSize() : 0;
+    const blksize = 4096;
+    return {
+      dev: 0,
+      ino: 0,
+      mode: node.mode,
+      nlink: 1,
+      uid: 0,
+      gid: 0,
+      rdev: 0,
+      size,
+      blksize,
+      blocks: Math.ceil(size / blksize),
+      atime: node.lastModified,
+      mtime: node.lastModified,
+      ctime: node.lastModified,
+    };
+  }
+
+  mkdir(path: string, options?: { recursive?: boolean; mode?: number }): void {
+    this.#tryWithWAL({ opp: "mkdir", args: [path, options] }, () => {
+      this._mkdirState(path, options);
+    });
+  }
+
+  _mkdirState(
+    path: string,
+    options?: { recursive?: boolean; mode?: number },
+  ): void {
+    const parts = this.#pathParts(path);
+    const newDirName = parts.pop()!;
+    let currentPath = [];
+    let node = this.state.root;
+    for (const part of parts) {
+      currentPath.push(path);
+      if (!node.children.hasOwnProperty(part)) {
+        if (options?.recursive) {
+          this.mkdir(currentPath.join("/"));
+        } else {
+          throw new FsError("ENOENT", "No such file or directory");
+        }
+      }
+      if (node.children[part].type !== "directory") {
+        throw new FsError("ENOTDIR", "Not a directory");
+      }
+      node = node.children[part] as DirectoryNode;
+    }
+    if (node.children.hasOwnProperty(newDirName)) {
+      throw new FsError("EEXIST", "File exists");
+    }
+    const newDir: DirectoryNode = {
+      type: "directory",
+      lastModified: Date.now(),
+      mode: options?.mode || INITIAL_MODE.DIR,
+      children: {},
+    };
+    node.children[newDirName] = newDir;
+  }
+
+  open(path: string, flags?: string, mode?: number): number {
+    const node = this.#resolvePath(path);
+    if (node.type !== "file") {
+      throw new FsError("EISDIR", "Is a directory");
+    }
+    const handleId = this.#nextHandleId();
+    this.#openHandlePaths.set(handleId, path);
+    this.#openHandleIds.set(path, handleId);
+    return handleId;
+  }
+
+  readdir(path: string): string[] {
+    const node = this.#resolvePath(path);
+    if (node.type !== "directory") {
+      throw new FsError("ENOTDIR", "Not a directory");
+    }
+    return Object.keys(node.children);
+  }
+
+  read(
+    fd: number,
+    buffer: Int8Array, // Buffer to read into
+    offset: number, // Offset in buffer to start writing to
+    length: number, // Number of bytes to read
+    position: number, // Position in file to read from
+  ): number {
+    const path = this.#getPathFromFd(fd);
+    const node = this.#resolvePath(path);
+    if (node.type !== "file") {
+      throw new FsError("EISDIR", "Is a directory");
+    }
+    const sh = this.#sh.get(node.backingFilename)!;
+    return sh.read(new Int8Array(buffer.buffer, offset, length), {
+      at: position,
+    });
+  }
+
+  rename(oldPath: string, newPath: string): void {
+    this.#tryWithWAL({ opp: "rename", args: [oldPath, newPath] }, () => {
+      this._renameState(oldPath, newPath, true);
+    });
+  }
+
+  _renameState(oldPath: string, newPath: string, doFileOps = false): void {
+    const oldPathParts = this.#pathParts(oldPath);
+    const oldFilename = oldPathParts.pop()!;
+    const oldParent = this.#resolvePath(
+      oldPathParts.join("/"),
+    ) as DirectoryNode;
+    if (!oldParent.children.hasOwnProperty(oldFilename)) {
+      throw new FsError("ENOENT", "No such file or directory");
+    }
+    const newPathParts = this.#pathParts(newPath);
+    const newFilename = newPathParts.pop()!;
+    const newParent = this.#resolvePath(
+      newPathParts.join("/"),
+    ) as DirectoryNode;
+    if (doFileOps && newParent.children.hasOwnProperty(newFilename)) {
+      // Overwrite, so return the underlying file to the pool
+      const node = newParent.children[newFilename]! as FileNode;
+      const sh = this.#sh.get(node.backingFilename)!;
+      sh.truncate(0);
+      this.state.pool.push(node.backingFilename);
+    }
+    newParent.children[newFilename] = oldParent.children[oldFilename]!;
+    delete oldParent.children[oldFilename];
+  }
+
+  rmdir(path: string): void {
+    this.#tryWithWAL({ opp: "rmdir", args: [path] }, () => {
+      this._rmdirState(path);
+    });
+  }
+
+  _rmdirState(path: string): void {
+    const pathParts = this.#pathParts(path);
+    const dirName = pathParts.pop()!;
+    const parent = this.#resolvePath(pathParts.join("/")) as DirectoryNode;
+    if (!parent.children.hasOwnProperty(dirName)) {
+      throw new FsError("ENOENT", "No such file or directory");
+    }
+    const node = parent.children[dirName]!;
+    if (node.type !== "directory") {
+      throw new FsError("ENOTDIR", "Not a directory");
+    }
+    if (Object.keys(node.children).length > 0) {
+      throw new FsError("ENOTEMPTY", "Directory not empty");
+    }
+    delete parent.children[dirName];
+  }
+
+  truncate(path: string, len = 0): void {
+    const node = this.#resolvePath(path);
+    if (node.type !== "file") {
+      throw new FsError("EISDIR", "Is a directory");
+    }
+    const sh = this.#sh.get(node.backingFilename);
+    if (!sh) {
+      throw new FsError("ENOENT", "No such file or directory");
+    }
+    sh.truncate(len);
+    this.#unsyncedSH.add(sh);
+  }
+
+  unlink(path: string): void {
+    this.#tryWithWAL({ opp: "unlink", args: [path] }, () => {
+      this._unlinkState(path, true);
+    });
+  }
+
+  _unlinkState(path: string, doFileOps = false): void {
+    const pathParts = this.#pathParts(path);
+    const filename = pathParts.pop()!;
+    const dir = this.#resolvePath(pathParts.join("/")) as DirectoryNode;
+    if (!dir.children.hasOwnProperty(filename)) {
+      throw new FsError("ENOENT", "No such file or directory");
+    }
+    const node = dir.children[filename]!;
+    if (node.type !== "file") {
+      throw new FsError("EISDIR", "Is a directory");
+    }
+    delete dir.children[filename];
+    if (doFileOps) {
+      const sh = this.#sh.get(node.backingFilename)!;
+      // We don't delete the file, it's truncated and returned to the pool
+      sh?.truncate(0);
+      this.#unsyncedSH.add(sh);
+      if (this.#openHandleIds.has(path)) {
+        this.#openHandlePaths.delete(this.#openHandleIds.get(path)!);
+        this.#openHandleIds.delete(path);
+      }
+    }
+    this.state.pool.push(node.backingFilename);
+  }
+
+  utimes(path: string, atime: number, mtime: number): void {
+    this.#tryWithWAL({ opp: "utimes", args: [path, atime, mtime] }, () => {
+      this._utimesState(path, atime, mtime);
+    });
+  }
+
+  _utimesState(path: string, atime: number, mtime: number): void {
+    const node = this.#resolvePath(path);
+    node.lastModified = mtime;
+  }
+
+  writeFile(
+    path: string,
+    data: string | Int8Array,
+    options?: { encoding?: string; mode?: number; flag?: string },
+  ): void {
+    const pathParts = this.#pathParts(path);
+    const filename = pathParts.pop()!;
+    const parent = this.#resolvePath(pathParts.join("/")) as DirectoryNode;
+
+    if (!parent.children.hasOwnProperty(filename)) {
+      if (this.state.pool.length === 0) {
+        throw new Error("No more file handles available in the pool");
+      }
+      const node: Node = {
+        type: "file",
+        lastModified: Date.now(),
+        mode: options?.mode || INITIAL_MODE.FILE,
+        backingFilename: this.state.pool.pop()!,
+      };
+      parent.children[filename] = node;
+      this.#logWAL({
+        opp: "createFileNode",
+        args: [path, node],
+      });
+    } else {
+      const node = parent.children[filename] as FileNode;
+      node.lastModified = Date.now();
+      this.#logWAL({
+        opp: "setLastModified",
+        args: [path, node.lastModified],
+      });
+    }
+    const node = parent.children[filename] as FileNode;
+    const sh = this.#sh.get(node.backingFilename)!;
+    // Files in pool are empty, only write if data is provided
+    if (data.length > 0) {
+      sh.write(
+        typeof data === "string"
+          ? new TextEncoder().encode(data)
+          : new Int8Array(data),
+        { at: 0 },
+      );
+      if (path.startsWith("/pg_wal")) {
+        this.#unsyncedSH.add(sh);
+      }
+    }
+  }
+
+  _createFileNodeState(path: string, node: FileNode): FileNode {
+    const pathParts = this.#pathParts(path);
+    const filename = pathParts.pop()!;
+    const parent = this.#resolvePath(pathParts.join("/")) as DirectoryNode;
+    parent.children[filename] = node;
+    // remove backingFilename from pool
+    const index = this.state.pool.indexOf(node.backingFilename);
+    if (index > -1) {
+      this.state.pool.splice(index, 1);
+    }
+    return node;
+  }
+
+  _setLastModifiedState(path: string, lastModified: number): void {
+    const node = this.#resolvePath(path);
+    node.lastModified = lastModified;
+  }
+
+  write(
+    fd: number,
+    buffer: Int8Array, // Buffer to read from
+    offset: number, // Offset in buffer to start reading from
+    length: number, // Number of bytes to write
+    position: number, // Position in file to write to
+  ): number {
+    const path = this.#getPathFromFd(fd);
+    const node = this.#resolvePath(path);
+    if (node.type !== "file") {
+      throw new FsError("EISDIR", "Is a directory");
+    }
+    const sh = this.#sh.get(node.backingFilename);
+    if (!sh) {
+      throw new FsError("EBADF", "Bad file descriptor");
+    }
+    const ret = sh.write(new Int8Array(buffer, offset, length), {
+      at: position,
+    });
+    if (path.startsWith("/pg_wal")) {
+      this.#unsyncedSH.add(sh);
+    }
+    return ret;
+  }
+
+  // Internal methods:
+
+  #tryWithWAL(entry: WALEntry, fn: () => void) {
+    const offset = this.#logWAL(entry);
+    try {
+      fn();
+    } catch (e) {
+      // Rollback WAL entry
+      this.#stateSH.truncate(offset);
+      throw e;
+    }
+  }
+
+  #logWAL(entry: WALEntry) {
+    const entryJSON = JSON.stringify(entry);
+    const stateAB = new TextEncoder().encode(`\n${entryJSON}`);
+    const offset = this.#stateSH.getSize();
+    this.#stateSH.write(stateAB, { at: offset });
+    this.#unsyncedSH.add(this.#stateSH);
+    return offset;
+  }
+
+  #pathParts(path: string): string[] {
+    return path.split("/").filter(Boolean);
+  }
+
+  #resolvePath(path: string, from?: DirectoryNode): Node {
+    const parts = this.#pathParts(path);
+    let node: Node = from || this.state.root;
+    for (const part of parts) {
+      if (node.type !== "directory") {
+        throw new FsError("ENOTDIR", "Not a directory");
+      }
+      if (!node.children.hasOwnProperty(part)) {
+        throw new FsError("ENOENT", "No such file or directory");
+      }
+      node = node.children[part]!;
+    }
+    return node;
+  }
+
+  #getPathFromFd(fd: number): string {
+    const path = this.#openHandlePaths.get(fd);
+    if (!path) {
+      throw new FsError("EBADF", "Bad file descriptor");
+    }
+    return path;
+  }
+
+  #nextHandleId(): number {
+    const id = ++this.#handleIdCounter;
+    while (this.#openHandlePaths.has(id)) {
+      this.#handleIdCounter++;
+    }
+    return id;
+  }
+
+  async #resolveOpfsDirectory(
+    path: string,
+    options?: {
+      from?: FileSystemDirectoryHandle;
+      create?: boolean;
+    },
+  ): Promise {
+    const parts = this.#pathParts(path);
+    let ah = options?.from || this.#opfsRootAh;
+    for (const part of parts) {
+      ah = await ah.getDirectoryHandle(part, { create: options?.create });
+    }
+    return ah;
+  }
+}
diff --git a/packages/pglite/src/fs/opfs-ahp/types.ts b/packages/pglite/src/fs/opfs-ahp/types.ts
new file mode 100644
index 00000000..acc8dd4e
--- /dev/null
+++ b/packages/pglite/src/fs/opfs-ahp/types.ts
@@ -0,0 +1,87 @@
+export type FsStats = {
+  dev: number;
+  ino: number;
+  mode: number;
+  nlink: number;
+  uid: number;
+  gid: number;
+  rdev: number;
+  size: number;
+  blksize: number;
+  blocks: number;
+  atime: number;
+  mtime: number;
+  ctime: number;
+};
+
+// TypeScript doesn't have a built-in type for FileSystemSyncAccessHandle
+export interface FileSystemSyncAccessHandle {
+  close(): void;
+  flush(): void;
+  getSize(): number;
+  read(buffer: ArrayBuffer, options: { at: number }): number;
+  truncate(newSize: number): void;
+  write(buffer: ArrayBuffer, options: { at: number }): number;
+}
+
+export const ERRNO_CODES = {
+  EBADF: 8,
+  EBADFD: 127,
+  EEXIST: 20,
+  EINVAL: 28,
+  EISDIR: 31,
+  ENODEV: 43,
+  ENOENT: 44,
+  ENOTDIR: 54,
+  ENOTEMPTY: 55,
+} as const;
+
+export class FsError extends Error {
+  code?: number;
+  constructor(code: number | keyof typeof ERRNO_CODES | null, message: string) {
+    super(message);
+    if (typeof code === "number") {
+      this.code = code;
+    } else if (typeof code === "string") {
+      this.code = ERRNO_CODES[code];
+    }
+  }
+}
+
+// State
+
+export interface State {
+  root: DirectoryNode;
+  pool: PoolFilenames;
+}
+
+export type PoolFilenames = Array;
+
+// WAL
+
+export interface WALEntry {
+  opp: string;
+  args: any[];
+}
+
+// Node tree
+
+export type NodeType = "file" | "directory";
+
+interface BaseNode {
+  type: NodeType;
+  lastModified: number;
+  mode: number;
+}
+
+export interface FileNode extends BaseNode {
+  type: "file";
+  backingFilename: string;
+}
+
+export interface DirectoryNode extends BaseNode {
+  type: "directory";
+  children: { [filename: string]: Node };
+}
+
+export type Node = FileNode | DirectoryNode;
diff --git a/packages/pglite/src/fs/types.ts b/packages/pglite/src/fs/types.ts
index bef970a6..46b7aa00 100644
--- a/packages/pglite/src/fs/types.ts
+++ b/packages/pglite/src/fs/types.ts
@@ -1,6 +1,11 @@
 import type { PostgresMod, FS } from "../postgresMod.js";
 
-export type FsType = "nodefs" | "idbfs" | "memoryfs";
+export type FsType =
+  | "nodefs"
+  | "idbfs"
+  | "memoryfs"
+  | "opfs-worker"
+  | "opfs-ahp";
 
 export interface FilesystemFactory {
   new (dataDir: string): Filesystem;
@@ -15,7 +20,7 @@ export interface Filesystem {
   /**
    * Sync the filesystem to the emscripten filesystem.
    */
-  syncToFs(FS: FS): Promise;
+  syncToFs(mod: FS, relaxedDurability?: boolean): Promise;
 
   /**
    * Sync the emscripten filesystem to the filesystem.
@@ -26,6 +31,11 @@ export interface Filesystem {
    * Dump the PGDATA dir from the filesystem to a gziped tarball.
    */
   dumpTar(FS: FS, dbname: string): Promise;
+
+  /**
+   * Close the filesystem.
+   */
+  close(): Promise;
 }
 
 export abstract class FilesystemBase implements Filesystem {
@@ -36,7 +46,8 @@ export abstract class FilesystemBase implements Filesystem {
   abstract emscriptenOpts(
     opts: Partial,
   ): Promise>;
-  async syncToFs(FS: FS) {}
+  async syncToFs(mod: FS, relaxedDurability?: boolean) {}
   async initialSyncFs(mod: FS) {}
   abstract dumpTar(mod: FS, dbname: string): Promise;
+  async close() {}
 }
diff --git a/packages/pglite/src/index.ts b/packages/pglite/src/index.ts
index 6be35516..85ca58a5 100644
--- a/packages/pglite/src/index.ts
+++ b/packages/pglite/src/index.ts
@@ -4,4 +4,6 @@ export * as types from "./types.js";
 export * as parse from "./parse.js";
 export * as messages from "pg-protocol/dist/messages.js";
 export * as protocol from "pg-protocol/dist/index.js";
+export { MemoryFS } from "./fs/memoryfs.js";
+export { IdbFs } from "./fs/idbfs.js";
 export { Mutex } from "async-mutex";
diff --git a/packages/pglite/src/interface.ts b/packages/pglite/src/interface.ts
index 36aa2fff..e9cbe960 100644
--- a/packages/pglite/src/interface.ts
+++ b/packages/pglite/src/interface.ts
@@ -1,4 +1,7 @@
-import type { BackendMessage } from "pg-protocol/dist/messages.js";
+import type {
+  BackendMessage,
+  NoticeMessage,
+} from "pg-protocol/dist/messages.js";
 import type { Filesystem } from "./fs/types.js";
 
 export type FilesystemType = "nodefs" | "idbfs" | "memoryfs";
@@ -15,10 +18,12 @@ export interface QueryOptions {
   rowMode?: RowMode;
   parsers?: ParserOptions;
   blob?: Blob | File;
+  onNotice?: (notice: NoticeMessage) => void;
 }
 
 export interface ExecProtocolOptions {
   syncToFs?: boolean;
+  onNotice?: (notice: NoticeMessage) => void;
 }
 
 export interface ExtensionSetupResult {
@@ -32,6 +37,7 @@ export interface ExtensionSetupResult {
 export type ExtensionSetup = (
   pg: PGliteInterface,
   emscriptenOpts: any,
+  clientOnly?: boolean,
 ) => Promise;
 
 export interface Extension {
diff --git a/packages/pglite/src/live/index.ts b/packages/pglite/src/live/index.ts
index 7a68281f..4b8ce368 100644
--- a/packages/pglite/src/live/index.ts
+++ b/packages/pglite/src/live/index.ts
@@ -10,12 +10,11 @@ import type {
   LiveChangesReturn,
   Change,
 } from "./interface";
+import { uuid } from "../utils.js";
 
-const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
-  // Counter use to generate unique IDs for live queries
-  // This is used to create temporary views and so are scoped to the current connection
-  let liveQueryCounter = 0;
+const MAX_RETRIES = 5;
 
+const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
   // The notify triggers are only ever added and never removed
   // Keep track of which triggers have been added to avoid adding them multiple times
   const tableNotifyTriggersAdded = new Set();
@@ -24,37 +23,57 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
     async query(
       query: string,
       params: any[] | undefined | null,
-      callback: (results: Results) => void
+      callback: (results: Results) => void,
     ) {
-      const id = liveQueryCounter++;
+      const id = uuid().replace(/-/g, "");
 
       let results: Results;
       let tables: { table_name: string; schema_name: string }[];
 
-      await pg.transaction(async (tx) => {
-        // Create a temporary view with the query
-        await tx.query(
-          `CREATE OR REPLACE TEMP VIEW live_query_${id}_view AS ${query}`,
-          params ?? []
-        );
+      const init = async () => {
+        await pg.transaction(async (tx) => {
+          // Create a temporary view with the query
+          await tx.query(
+            `CREATE OR REPLACE TEMP VIEW live_query_${id}_view AS ${query}`,
+            params ?? [],
+          );
 
-        // Get the tables used in the view and add triggers to notify when they change
-        tables = await getTablesForView(tx, `live_query_${id}_view`);
-        await addNotifyTriggersToTables(tx, tables, tableNotifyTriggersAdded);
+          // Get the tables used in the view and add triggers to notify when they change
+          tables = await getTablesForView(tx, `live_query_${id}_view`);
+          await addNotifyTriggersToTables(tx, tables, tableNotifyTriggersAdded);
 
-        // Create prepared statement to get the results
-        await tx.exec(`
-          PREPARE live_query_${id}_get AS
-          SELECT * FROM live_query_${id}_view;
-        `);
+          // Create prepared statement to get the results
+          await tx.exec(`
+            PREPARE live_query_${id}_get AS
+            SELECT * FROM live_query_${id}_view;
+          `);
 
-        // Get the initial results
-        results = await tx.query(`EXECUTE live_query_${id}_get;`);
-      });
+          // Get the initial results
+          results = await tx.query(`EXECUTE live_query_${id}_get;`);
+        });
+      };
+      await init();
 
       // Function to refresh the query
-      const refresh = async () => {
-        results = await pg.query(`EXECUTE live_query_${id}_get;`);
+      const refresh = async (count = 0) => {
+        try {
+          results = await pg.query(`EXECUTE live_query_${id}_get;`);
+        } catch (e) {
+          const msg = (e as Error).message;
+          if (
+            msg == `prepared statement "live_query_${id}_get" does not exist`
+          ) {
+            // If the prepared statement does not exist, reset and try again
+            // This can happen if using the multi-tab worker
+            if (count > MAX_RETRIES) {
+              throw e;
+            }
+            await init();
+            refresh(count + 1);
+          } else {
+            throw e;
+          }
+        }
         callback(results);
       };
 
@@ -65,7 +84,7 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
           `table_change__${table.schema_name}__${table.table_name}`,
           async () => {
             refresh();
-          }
+          },
         );
         unsubList.push(unsub);
       }
@@ -96,137 +115,171 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
       query: string,
       params: any[] | undefined | null,
       key: string,
-      callback: (changes: Array>) => void
+      callback: (changes: Array>) => void,
     ) {
-      const id = liveQueryCounter++;
+      const id = uuid().replace(/-/g, "");
+
       let tables: { table_name: string; schema_name: string }[];
       let stateSwitch: 1 | 2 = 1;
       let changes: Results>;
 
-      await pg.transaction(async (tx) => {
-        // Create a temporary view with the query
-        await tx.query(
-          `CREATE OR REPLACE TEMP VIEW live_query_${id}_view AS ${query}`,
-          params ?? []
-        );
-
-        // Get the tables used in the view and add triggers to notify when they change
-        tables = await getTablesForView(tx, `live_query_${id}_view`);
-        await addNotifyTriggersToTables(tx, tables, tableNotifyTriggersAdded);
-
-        // Get the columns of the view
-        const columns = [
-          ...(
-            await tx.query(`
-              SELECT column_name, data_type 
-              FROM information_schema.columns 
-              WHERE table_name = 'live_query_${id}_view'
-            `)
-          ).rows,
-          { column_name: "__after__", data_type: "integer" },
-        ];
-
-        // Init state tables as empty temp table
-        await tx.exec(`
-          CREATE TEMP TABLE live_query_${id}_state1 (LIKE live_query_${id}_view INCLUDING ALL);
-          CREATE TEMP TABLE live_query_${id}_state2 (LIKE live_query_${id}_view INCLUDING ALL);
-        `);
+      const init = async () => {
+        await pg.transaction(async (tx) => {
+          // Create a temporary view with the query
+          await tx.query(
+            `CREATE OR REPLACE TEMP VIEW live_query_${id}_view AS ${query}`,
+            params ?? [],
+          );
 
-        // Create Diff views and prepared statements
-        for (const curr of [1, 2]) {
-          const prev = curr === 1 ? 2 : 1;
+          // Get the tables used in the view and add triggers to notify when they change
+          tables = await getTablesForView(tx, `live_query_${id}_view`);
+          await addNotifyTriggersToTables(tx, tables, tableNotifyTriggersAdded);
+
+          // Get the columns of the view
+          const columns = [
+            ...(
+              await tx.query(`
+                SELECT column_name, data_type, udt_name
+                FROM information_schema.columns 
+                WHERE table_name = 'live_query_${id}_view'
+              `)
+            ).rows,
+            { column_name: "__after__", data_type: "integer" },
+          ];
+
+          // Init state tables as empty temp table
           await tx.exec(`
-            PREPARE live_query_${id}_diff${curr} AS
-            WITH
-              prev AS (SELECT LAG("${key}") OVER () as __after__, * FROM live_query_${id}_state${prev}),
-              curr AS (SELECT LAG("${key}") OVER () as __after__, * FROM live_query_${id}_state${curr}),
-              data_diff AS (
-                -- INSERT operations: Include all columns
-                SELECT 
-                  'INSERT' AS __op__,
-                  ${columns
-                    .map(
-                      ({ column_name }) =>
-                        `curr."${column_name}" AS "${column_name}"`
-                    )
-                    .join(",\n")},
-                  ARRAY[]::text[] AS __changed_columns__
-                FROM curr
-                LEFT JOIN prev ON curr.${key} = prev.${key}
-                WHERE prev.${key} IS NULL
-              UNION ALL
-                -- DELETE operations: Include only the primary key
-                SELECT 
-                  'DELETE' AS __op__,
-                  ${columns
-                    .map(({ column_name, data_type }) => {
-                      if (column_name === key) {
-                        return `prev."${column_name}" AS "${column_name}"`;
-                      } else {
-                        return `NULL::${data_type} AS "${column_name}"`;
-                      }
-                    })
-                    .join(",\n")},
-                    ARRAY[]::text[] AS __changed_columns__
-                FROM prev
-                LEFT JOIN curr ON prev.${key} = curr.${key}
-                WHERE curr.${key} IS NULL
-              UNION ALL
-                -- UPDATE operations: Include only changed columns
-                SELECT 
-                  'UPDATE' AS __op__,
-                  ${columns
-                    .map(({ column_name, data_type }) =>
-                      column_name === key
-                        ? `curr."${column_name}" AS "${column_name}"`
-                        : `CASE 
-                            WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" 
-                            THEN curr."${column_name}"
-                            ELSE NULL::${data_type} 
-                            END AS "${column_name}"`
-                    )
-                    .join(",\n")},
-                    ARRAY(SELECT unnest FROM unnest(ARRAY[${columns
-                      .filter(({ column_name }) => column_name !== key)
+            CREATE TEMP TABLE live_query_${id}_state1 (LIKE live_query_${id}_view INCLUDING ALL);
+            CREATE TEMP TABLE live_query_${id}_state2 (LIKE live_query_${id}_view INCLUDING ALL);
+          `);
+
+          // Create Diff views and prepared statements
+          for (const curr of [1, 2]) {
+            const prev = curr === 1 ? 2 : 1;
+            await tx.exec(`
+              PREPARE live_query_${id}_diff${curr} AS
+              WITH
+                prev AS (SELECT LAG("${key}") OVER () as __after__, * FROM live_query_${id}_state${prev}),
+                curr AS (SELECT LAG("${key}") OVER () as __after__, * FROM live_query_${id}_state${curr}),
+                data_diff AS (
+                  -- INSERT operations: Include all columns
+                  SELECT 
+                    'INSERT' AS __op__,
+                    ${columns
                       .map(
                         ({ column_name }) =>
-                          `CASE
-                            WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" 
-                            THEN '${column_name}' 
-                            ELSE NULL 
-                            END`
+                          `curr."${column_name}" AS "${column_name}"`,
                       )
-                      .join(
-                        ", "
-                      )}]) WHERE unnest IS NOT NULL) AS __changed_columns__
-                FROM curr
-                INNER JOIN prev ON curr.${key} = prev.${key}
-                WHERE NOT (curr IS NOT DISTINCT FROM prev)
-              )
-            SELECT * FROM data_diff;
-          `);
-        }
-      });
+                      .join(",\n")},
+                    ARRAY[]::text[] AS __changed_columns__
+                  FROM curr
+                  LEFT JOIN prev ON curr.${key} = prev.${key}
+                  WHERE prev.${key} IS NULL
+                UNION ALL
+                  -- DELETE operations: Include only the primary key
+                  SELECT 
+                    'DELETE' AS __op__,
+                    ${columns
+                      .map(({ column_name, data_type, udt_name }) => {
+                        if (column_name === key) {
+                          return `prev."${column_name}" AS "${column_name}"`;
+                        } else {
+                          return `NULL::${data_type == "USER-DEFINED" ? udt_name : data_type} AS "${column_name}"`;
+                        }
+                      })
+                      .join(",\n")},
+                      ARRAY[]::text[] AS __changed_columns__
+                  FROM prev
+                  LEFT JOIN curr ON prev.${key} = curr.${key}
+                  WHERE curr.${key} IS NULL
+                UNION ALL
+                  -- UPDATE operations: Include only changed columns
+                  SELECT 
+                    'UPDATE' AS __op__,
+                    ${columns
+                      .map(({ column_name, data_type, udt_name }) =>
+                        column_name === key
+                          ? `curr."${column_name}" AS "${column_name}"`
+                          : `CASE 
+                              WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" 
+                              THEN curr."${column_name}"
+                              ELSE NULL::${data_type == "USER-DEFINED" ? udt_name : data_type} 
+                              END AS "${column_name}"`,
+                      )
+                      .join(",\n")},
+                      ARRAY(SELECT unnest FROM unnest(ARRAY[${columns
+                        .filter(({ column_name }) => column_name !== key)
+                        .map(
+                          ({ column_name }) =>
+                            `CASE
+                              WHEN curr."${column_name}" IS DISTINCT FROM prev."${column_name}" 
+                              THEN '${column_name}' 
+                              ELSE NULL 
+                              END`,
+                        )
+                        .join(
+                          ", ",
+                        )}]) WHERE unnest IS NOT NULL) AS __changed_columns__
+                  FROM curr
+                  INNER JOIN prev ON curr.${key} = prev.${key}
+                  WHERE NOT (curr IS NOT DISTINCT FROM prev)
+                )
+              SELECT * FROM data_diff;
+            `);
+          }
+        });
+      };
 
-      const refresh = async () => {
-        await pg.transaction(async (tx) => {
-          // Populate the state table
-          await tx.exec(`
-            DELETE FROM live_query_${id}_state${stateSwitch};
-            INSERT INTO live_query_${id}_state${stateSwitch} 
-              SELECT * FROM live_query_${id}_view;
-          `);
+      await init();
 
-          // Get the changes
-          changes = await tx.query(
-            `EXECUTE live_query_${id}_diff${stateSwitch};`
-          );
-        });
+      const refresh = async () => {
+        let reset = false;
+        for (let i = 0; i < 5; i++) {
+          try {
+            await pg.transaction(async (tx) => {
+              // Populate the state table
+              await tx.exec(`
+                DELETE FROM live_query_${id}_state${stateSwitch};
+                INSERT INTO live_query_${id}_state${stateSwitch} 
+                  SELECT * FROM live_query_${id}_view;
+              `);
+
+              // Get the changes
+              changes = await tx.query(
+                `EXECUTE live_query_${id}_diff${stateSwitch};`,
+              );
+            });
+            break;
+          } catch (e) {
+            const msg = (e as Error).message;
+            if (
+              msg ==
+              `relation "live_query_${id}_state${stateSwitch}" does not exist`
+            ) {
+              // If the state table does not exist, reset and try again
+              // This can happen if using the multi-tab worker
+              reset = true;
+              await init();
+              continue;
+            } else {
+              throw e;
+            }
+          }
+        }
 
         // Switch state
         stateSwitch = stateSwitch === 1 ? 2 : 1;
 
-        callback(changes!.rows);
+        callback([
+          ...(reset
+            ? [
+                {
+                  __op__: "RESET" as const,
+                },
+              ]
+            : []),
+          ...changes!.rows,
+        ]);
       };
 
       // Setup the listeners
@@ -236,7 +289,7 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
           `table_change__${table.schema_name}__${table.table_name}`,
           async () => {
             refresh();
-          }
+          },
         );
         unsubList.push(unsub);
       }
@@ -261,7 +314,7 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
       // Fields
       const fields = changes!.fields.filter(
         (field) =>
-          !["__after__", "__op__", "__changed_columns__"].includes(field.name)
+          !["__after__", "__op__", "__changed_columns__"].includes(field.name),
       );
 
       // Return the initial results
@@ -277,7 +330,7 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
       query: string,
       params: any[] | undefined | null,
       key: string,
-      callback: (results: Results>) => void
+      callback: (results: Results>) => void,
     ) {
       const rowsMap: Map = new Map();
       const afterMap: Map = new Map();
@@ -297,6 +350,10 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
               ...obj
             } = change as typeof change & { [key: string]: any };
             switch (op) {
+              case "RESET":
+                rowsMap.clear();
+                afterMap.clear();
+                break;
               case "INSERT":
                 rowsMap.set(obj[key], obj);
                 afterMap.set(obj.__after__, obj[key]);
@@ -340,7 +397,7 @@ const setup = async (pg: PGliteInterface, emscriptenOpts: any) => {
               fields,
             });
           }
-        }
+        },
       );
 
       firstRun = false;
@@ -378,7 +435,7 @@ export const live = {
  */
 async function getTablesForView(
   tx: Transaction | PGliteInterface,
-  viewName: string
+  viewName: string,
 ): Promise<{ table_name: string; schema_name: string }[]> {
   return (
     await tx.query<{
@@ -399,7 +456,7 @@ async function getTablesForView(
         )
         AND d.deptype = 'n';
       `,
-      [viewName]
+      [viewName],
     )
   ).rows.filter((row) => row.table_name !== viewName);
 }
@@ -412,14 +469,14 @@ async function getTablesForView(
 async function addNotifyTriggersToTables(
   tx: Transaction | PGliteInterface,
   tables: { table_name: string; schema_name: string }[],
-  tableNotifyTriggersAdded: Set
+  tableNotifyTriggersAdded: Set,
 ) {
   const triggers = tables
     .filter(
       (table) =>
         !tableNotifyTriggersAdded.has(
-          `${table.schema_name}_${table.table_name}`
-        )
+          `${table.schema_name}_${table.table_name}`,
+        ),
     )
     .map((table) => {
       return `
@@ -439,6 +496,6 @@ async function addNotifyTriggersToTables(
     await tx.exec(triggers);
   }
   tables.map((table) =>
-    tableNotifyTriggersAdded.add(`${table.schema_name}_${table.table_name}`)
+    tableNotifyTriggersAdded.add(`${table.schema_name}_${table.table_name}`),
   );
 }
diff --git a/packages/pglite/src/live/interface.ts b/packages/pglite/src/live/interface.ts
index 9bbb7814..6c3a3661 100644
--- a/packages/pglite/src/live/interface.ts
+++ b/packages/pglite/src/live/interface.ts
@@ -77,4 +77,12 @@ export type ChangeUpdate = {} & {
   __after__: number;
 } & T;
 
-export type Change = ChangeInsert | ChangeDelete | ChangeUpdate;
+export type ChangeReset = {
+  __op__: "RESET";
+} & T;
+
+export type Change =
+  | ChangeInsert
+  | ChangeDelete
+  | ChangeUpdate
+  | ChangeReset;
diff --git a/packages/pglite/src/pglite.ts b/packages/pglite/src/pglite.ts
index 7df875e4..46372b90 100644
--- a/packages/pglite/src/pglite.ts
+++ b/packages/pglite/src/pglite.ts
@@ -110,13 +110,28 @@ export class PGlite implements PGliteInterface {
     // Save the extensions for later use
     this.#extensions = options.extensions ?? {};
 
-    // Save the extensions for later use
-    this.#extensions = options.extensions ?? {};
-
     // Initialize the database, and store the promise so we can wait for it to be ready
     this.waitReady = this.#init(options ?? {});
   }
 
+  /**
+   * Create a new PGlite instance with extensions on the Typescript interface
+   * (The main constructor does enable extensions, however due to the limitations
+   * of Typescript, the extensions are not available on the instance interface)
+   * @param dataDir The directory to store the database files
+   *                Prefix with idb:// to use indexeddb filesystem in the browser
+   *                Use memory:// to use in-memory filesystem
+   * @param options Optional options
+   * @returns A promise that resolves to the PGlite instance when it's ready.
+   */
+  static async create(
+    options?: O,
+  ): Promise> {
+    const pg = new PGlite(options);
+    await pg.waitReady;
+    return pg as any;
+  }
+
   /**
    * Initialize the database
    * @returns A promise that resolves when the database is ready
@@ -294,6 +309,13 @@ export class PGlite implements PGliteInterface {
     }
   }
 
+  /**
+   * The Postgres Emscripten Module
+   */
+  get Module() {
+    return this.mod!;
+  }
+
   /**
    * The ready state of the database
    */
@@ -400,19 +422,22 @@ export class PGlite implements PGliteInterface {
               text: query,
               types: parsedParams.map(([, type]) => type),
             }),
+            options,
           )),
           ...(await this.#execProtocolNoSync(
             serialize.bind({
               values: parsedParams.map(([val]) => val),
             }),
+            options,
           )),
           ...(await this.#execProtocolNoSync(
             serialize.describe({ type: "P" }),
+            options,
           )),
-          ...(await this.#execProtocolNoSync(serialize.execute({}))),
+          ...(await this.#execProtocolNoSync(serialize.execute({}), options)),
         ];
       } finally {
-        await this.#execProtocolNoSync(serialize.sync());
+        await this.#execProtocolNoSync(serialize.sync(), options);
       }
       this.#cleanupBlob();
       if (!this.#inTransaction) {
@@ -448,9 +473,12 @@ export class PGlite implements PGliteInterface {
       await this.#handleBlob(options?.blob);
       let results;
       try {
-        results = await this.#execProtocolNoSync(serialize.query(query));
+        results = await this.#execProtocolNoSync(
+          serialize.query(query),
+          options,
+        );
       } finally {
-        await this.#execProtocolNoSync(serialize.sync());
+        await this.#execProtocolNoSync(serialize.sync(), options);
       }
       this.#cleanupBlob();
       if (!this.#inTransaction) {
@@ -608,7 +636,7 @@ export class PGlite implements PGliteInterface {
    */
   async execProtocol(
     message: Uint8Array,
-    { syncToFs = true }: ExecProtocolOptions = {},
+    { syncToFs = true, onNotice }: ExecProtocolOptions = {},
   ): Promise> {
     const data = await this.execProtocolRaw(message, { syncToFs });
     const results: Array<[BackendMessage, Uint8Array]> = [];
@@ -618,9 +646,14 @@ export class PGlite implements PGliteInterface {
         this.#parser = new Parser(); // Reset the parser
         throw msg;
         // TODO: Do we want to wrap the error in a custom error?
-      } else if (msg instanceof NoticeMessage && this.debug > 0) {
-        // Notice messages are warnings, we should log them
-        console.warn(msg);
+      } else if (msg instanceof NoticeMessage) {
+        if (this.debug > 0) {
+          // Notice messages are warnings, we should log them
+          console.warn(msg);
+        }
+        if (onNotice) {
+          onNotice(msg);
+        }
       } else if (msg instanceof CommandCompleteMessage) {
         // Keep track of the transaction state
         switch (msg.text) {
@@ -654,8 +687,9 @@ export class PGlite implements PGliteInterface {
 
   async #execProtocolNoSync(
     message: Uint8Array,
+    options: ExecProtocolOptions = {},
   ): Promise> {
-    return await this.execProtocol(message, { syncToFs: false });
+    return await this.execProtocol(message, { ...options, syncToFs: false });
   }
 
   /**
@@ -671,7 +705,7 @@ export class PGlite implements PGliteInterface {
     const doSync = async () => {
       await this.#fsSyncMutex.runExclusive(async () => {
         this.#fsSyncScheduled = false;
-        await this.fs!.syncToFs(this.mod!.FS);
+        await this.fs!.syncToFs(this.mod!.FS, this.#relaxedDurability);
       });
     };
 
@@ -746,22 +780,6 @@ export class PGlite implements PGliteInterface {
     this.#globalNotifyListeners.delete(callback);
   }
 
-  /**
-   * Create a new PGlite instance with extensions on the Typescript interface
-   * (The main constructor does enable extensions, however due to the limitations
-   * of Typescript, the extensions are not available on the instance interface)
-   * @param dataDir The directory to store the database files
-   *                Prefix with idb:// to use indexeddb filesystem in the browser
-   *                Use memory:// to use in-memory filesystem
-   * @param options Optional options
-   * @returns A new PGlite instance with extensions
-   */
-  static withExtensions(
-    options?: O,
-  ): PGlite & PGliteInterfaceExtensions {
-    return new PGlite(options) as any;
-  }
-
   /**
    * Dump the PGDATA dir from the filesystem to a gziped tarball.
    * @returns The tarball as a File object where available, and fallback to a Blob
diff --git a/packages/pglite/src/postgresMod.ts b/packages/pglite/src/postgresMod.ts
index 4be4474f..9587ff69 100644
--- a/packages/pglite/src/postgresMod.ts
+++ b/packages/pglite/src/postgresMod.ts
@@ -1,6 +1,5 @@
 //@ts-ignore
-// This file in placed in the build from ../release/postgres.js
-import PostgresModFactory from "./postgres.js";
+import PostgresModFactory from "../release/postgres.js";
 
 // Uses the types from @types/emscripten
 
diff --git a/packages/pglite/src/utils.ts b/packages/pglite/src/utils.ts
index 89c29356..b023fd57 100644
--- a/packages/pglite/src/utils.ts
+++ b/packages/pglite/src/utils.ts
@@ -29,3 +29,42 @@ export async function makeLocateFile() {
     return url?.toString() ?? "";
   };
 }
+
+export const uuid = (): string => {
+  // best case, `crypto.randomUUID` is available
+  if (globalThis.crypto?.randomUUID) {
+    return globalThis.crypto.randomUUID();
+  }
+
+  const bytes = new Uint8Array(16);
+
+  if (globalThis.crypto?.getRandomValues) {
+    // `crypto.getRandomValues` is available even in non-secure contexts
+    globalThis.crypto.getRandomValues(bytes);
+  } else {
+    // fallback to Math.random, if the Crypto API is completely missing
+    for (let i = 0; i < bytes.length; i++) {
+      bytes[i] = Math.floor(Math.random() * 256);
+    }
+  }
+
+  bytes[6] = (bytes[6] & 0x0f) | 0x40; // Set the 4 most significant bits to 0100
+  bytes[8] = (bytes[8] & 0x3f) | 0x80; // Set the 2 most significant bits to 10
+
+  const hexValues: string[] = [];
+  bytes.forEach((byte) => {
+    hexValues.push(byte.toString(16).padStart(2, "0"));
+  });
+
+  return (
+    hexValues.slice(0, 4).join("") +
+    "-" +
+    hexValues.slice(4, 6).join("") +
+    "-" +
+    hexValues.slice(6, 8).join("") +
+    "-" +
+    hexValues.slice(8, 10).join("") +
+    "-" +
+    hexValues.slice(10).join("")
+  );
+};
diff --git a/packages/pglite/src/worker/index.ts b/packages/pglite/src/worker/index.ts
index 4358a0d4..15f205b2 100644
--- a/packages/pglite/src/worker/index.ts
+++ b/packages/pglite/src/worker/index.ts
@@ -1,106 +1,415 @@
-import * as Comlink from "comlink";
 import type {
+  DebugLevel,
+  Extensions,
   PGliteInterface,
+  PGliteInterfaceExtensions,
   PGliteOptions,
-  FilesystemType,
-  DebugLevel,
-  Results,
   QueryOptions,
+  Results,
+  Transaction,
 } from "../interface.js";
+import { uuid } from "../utils.js";
 import type { BackendMessage } from "pg-protocol/dist/messages.js";
-import { parseDataDir } from "../fs/index.js";
-import type { Worker as WorkerInterface } from "./process.js";
+
+export type PGliteWorkerOptions = PGliteOptions & {
+  meta?: any;
+  id?: string;
+};
 
 export class PGliteWorker implements PGliteInterface {
-  readonly dataDir?: string;
-  // @ts-ignore
-  readonly fsType: FilesystemType;
-  readonly waitReady: Promise;
-  readonly debug: DebugLevel = 0;
+  #initPromise: Promise;
+  #debug: DebugLevel = 0;
 
   #ready = false;
   #closed = false;
+  #isLeader = false;
+
+  #eventTarget = new EventTarget();
 
-  #worker: WorkerInterface;
-  #options: PGliteOptions;
+  #tabId: string;
+
+  #connected = false;
+
+  #workerProcess: Worker;
+  #workerID?: string;
+  #workerHerePromise?: Promise;
+  #workerReadyPromise?: Promise;
+
+  #broadcastChannel?: BroadcastChannel;
+  #tabChannel?: BroadcastChannel;
+  #releaseTabCloseLock?: () => void;
 
   #notifyListeners = new Map void>>();
   #globalNotifyListeners = new Set<
     (channel: string, payload: string) => void
   >();
 
-  constructor(dataDir: string, options?: PGliteOptions) {
-    const { dataDir: dir, fsType } = parseDataDir(dataDir);
-    this.dataDir = dir;
-    // @ts-ignore
-    this.fsType = fsType;
-    this.#options = options ?? {};
-    this.debug = options?.debug ?? 0;
-
-    this.#worker = Comlink.wrap(
-      // the below syntax is required by webpack in order to
-      // identify the worker properly during static analysis
-      // see: https://webpack.js.org/guides/web-workers/
-      new Worker(new URL("./process.js", import.meta.url), { type: "module" }),
-    );
+  #extensions: Extensions;
+  #extensionsClose: Array<() => Promise> = [];
+
+  constructor(worker: Worker, options?: PGliteWorkerOptions) {
+    this.#workerProcess = worker;
+    this.#tabId = uuid();
+    this.#extensions = options?.extensions ?? {};
+
+    this.#workerHerePromise = new Promise((resolve) => {
+      this.#workerProcess.addEventListener(
+        "message",
+        (event) => {
+          if (event.data.type === "here") {
+            resolve();
+          } else {
+            throw new Error("Invalid message");
+          }
+        },
+        { once: true },
+      );
+    });
+
+    this.#workerReadyPromise = new Promise((resolve) => {
+      const callback = (event: MessageEvent) => {
+        if (event.data.type === "ready") {
+          this.#workerID = event.data.id;
+          this.#workerProcess.removeEventListener("message", callback);
+          resolve();
+        }
+      };
+      this.#workerProcess.addEventListener("message", callback);
+    });
+
+    this.#initPromise = this.#init(options);
+  }
+
+  /**
+   * Create a new PGlite instance with extensions on the Typescript interface
+   * This also awaits the instance to be ready before resolving
+   * (The main constructor does enable extensions, however due to the limitations
+   * of Typescript, the extensions are not available on the instance interface)
+   * @param worker The worker to use
+   * @param options Optional options
+   * @returns A promise that resolves to the PGlite instance when it's ready.
+   */
+  static async create(
+    worker: Worker,
+    options?: O,
+  ): Promise> {
+    const pg = new PGliteWorker(worker, options);
+    await pg.#initPromise;
+    return pg as PGliteWorker & PGliteInterfaceExtensions;
+  }
+
+  async #init(options: PGliteWorkerOptions = {}) {
+    // Setup the extensions
+    for (const [extName, ext] of Object.entries(this.#extensions)) {
+      if (ext instanceof URL) {
+        throw new Error(
+          "URL extensions are not supported on the client side of a worker",
+        );
+      } else {
+        const extRet = await ext.setup(this, {}, true);
+        if (extRet.emscriptenOpts) {
+          console.warn(
+            `PGlite extension ${extName} returned emscriptenOpts, these are not supported on the client side of a worker`,
+          );
+        }
+        if (extRet.namespaceObj) {
+          (this as any)[extName] = extRet.namespaceObj;
+        }
+        if (extRet.bundlePath) {
+          console.warn(
+            `PGlite extension ${extName} returned bundlePath, this is not supported on the client side of a worker`,
+          );
+        }
+        if (extRet.init) {
+          await extRet.init();
+        }
+        if (extRet.close) {
+          this.#extensionsClose.push(extRet.close);
+        }
+      }
+    }
+
+    // Wait for the worker let us know it's here
+    await this.#workerHerePromise;
+
+    // Send the worker the options
+    const { extensions, ...workerOptions } = options;
+    this.#workerProcess.postMessage({
+      type: "init",
+      options: workerOptions,
+    });
+
+    // Wait for the worker let us know it's ready
+    await this.#workerReadyPromise;
+
+    // Acquire the tab close lock, this is released then the tab, or this
+    // PGliteWorker instance, is closed
+    const tabCloseLockId = `pglite-tab-close:${this.#tabId}`;
+    this.#releaseTabCloseLock = await acquireLock(tabCloseLockId);
 
-    // pass unparsed dataDir value
-    this.waitReady = this.#init(dataDir);
+    // Start the broadcast channel used to communicate with tabs and leader election
+    const broadcastChannelId = `pglite-broadcast:${this.#workerID}`;
+    this.#broadcastChannel = new BroadcastChannel(broadcastChannelId);
+
+    // Start the tab channel used to communicate with the leader directly
+    const tabChannelId = `pglite-tab:${this.#tabId}`;
+    this.#tabChannel = new BroadcastChannel(tabChannelId);
+
+    this.#broadcastChannel.addEventListener("message", async (event) => {
+      if (event.data.type === "leader-here") {
+        this.#connected = false;
+        this.#eventTarget.dispatchEvent(new Event("leader-change"));
+        this.#leaderNotifyLoop();
+      } else if (event.data.type === "notify") {
+        this.#receiveNotification(event.data.channel, event.data.payload);
+      }
+    });
+
+    this.#tabChannel.addEventListener("message", async (event) => {
+      if (event.data.type === "connected") {
+        this.#connected = true;
+        this.#eventTarget.dispatchEvent(new Event("connected"));
+        this.#debug = await this.#rpc("getDebugLevel");
+        this.#ready = true;
+      }
+    });
+
+    this.#workerProcess.addEventListener("message", async (event) => {
+      if (event.data.type === "leader-now") {
+        this.#isLeader = true;
+        this.#eventTarget.dispatchEvent(new Event("leader-change"));
+      }
+    });
+
+    this.#leaderNotifyLoop();
+  }
+
+  async #leaderNotifyLoop() {
+    if (!this.#connected) {
+      this.#broadcastChannel!.postMessage({
+        type: "tab-here",
+        id: this.#tabId,
+      });
+      setTimeout(() => this.#leaderNotifyLoop(), 16);
+    }
   }
 
-  async #init(dataDir: string) {
-    await this.#worker.init(
-      dataDir,
-      this.#options,
-      Comlink.proxy(this.receiveNotification.bind(this)),
+  async #rpc(
+    method: Method,
+    ...args: Parameters
+  ): Promise> {
+    const callId = uuid();
+    const message: WorkerRpcCall = {
+      type: "rpc-call",
+      callId,
+      method,
+      args,
+    };
+    this.#tabChannel!.postMessage(message);
+    return await new Promise>(
+      (resolve, reject) => {
+        const listener = (event: MessageEvent) => {
+          if (event.data.callId !== callId) return;
+          cleanup();
+          const message: WorkerRpcResponse = event.data;
+          if (message.type === "rpc-return") {
+            resolve(message.result);
+          } else if (message.type === "rpc-error") {
+            const error = new Error(message.error.message);
+            Object.assign(error, message.error);
+            reject(error);
+          } else {
+            reject(new Error("Invalid message"));
+          }
+        };
+        const leaderChangeListener = () => {
+          // If the leader changes, throw an error to reject the promise
+          cleanup();
+          reject(new LeaderChangedError());
+        };
+        const cleanup = () => {
+          this.#tabChannel!.removeEventListener("message", listener);
+          this.#eventTarget.removeEventListener(
+            "leader-change",
+            leaderChangeListener,
+          );
+        };
+        this.#eventTarget.addEventListener(
+          "leader-change",
+          leaderChangeListener,
+        );
+        this.#tabChannel!.addEventListener("message", listener);
+      },
     );
-    this.#ready = true;
   }
 
+  get waitReady() {
+    return new Promise(async (resolve) => {
+      await this.#initPromise;
+      if (!this.#connected) {
+        resolve(
+          new Promise((resolve) => {
+            this.#eventTarget.addEventListener("connected", () => {
+              resolve();
+            });
+          }),
+        );
+      } else {
+        resolve();
+      }
+    });
+  }
+
+  get debug() {
+    return this.#debug;
+  }
+
+  /**
+   * The ready state of the database
+   */
   get ready() {
     return this.#ready;
   }
 
+  /**
+   * The closed state of the database
+   */
   get closed() {
     return this.#closed;
   }
 
+  /**
+   * The leader state of this tab
+   */
+  get isLeader() {
+    return this.#isLeader;
+  }
+
+  /**
+   * Close the database
+   * @returns Promise that resolves when the connection to shared PGlite is closed
+   */
   async close() {
-    await this.#worker.close();
+    if (this.#closed) {
+      return;
+    }
     this.#closed = true;
+    this.#broadcastChannel?.close();
+    this.#tabChannel?.close();
+    this.#releaseTabCloseLock?.();
+    this.#workerProcess.terminate();
   }
 
+  /**
+   * Execute a single SQL statement
+   * This uses the "Extended Query" postgres wire protocol message.
+   * @param query The query to execute
+   * @param params Optional parameters for the query
+   * @returns The result of the query
+   */
   async query(
     query: string,
     params?: any[],
     options?: QueryOptions,
   ): Promise> {
-    return this.#worker.query(query, params, options) as Promise>;
+    await this.waitReady;
+    return (await this.#rpc("query", query, params, options)) as Results;
   }
 
+  /**
+   * Execute a SQL query, this can have multiple statements.
+   * This uses the "Simple Query" postgres wire protocol message.
+   * @param query The query to execute
+   * @returns The result of the query
+   */
   async exec(query: string, options?: QueryOptions): Promise> {
-    return this.#worker.exec(query, options);
+    await this.waitReady;
+    return (await this.#rpc("exec", query, options)) as Array;
   }
 
-  async transaction(callback: (tx: any) => Promise) {
-    const callbackProxy = Comlink.proxy(callback);
-    return this.#worker.transaction(callbackProxy);
+  /**
+   * Execute a transaction
+   * @param callback A callback function that takes a transaction object
+   * @returns The result of the transaction
+   */
+  async transaction(
+    callback: (tx: Transaction) => Promise,
+  ): Promise {
+    await this.waitReady;
+    const txId = await this.#rpc("transactionStart");
+    let ret: T | undefined;
+    try {
+      ret = await callback({
+        query: async (query, params, options) => {
+          return await this.#rpc(
+            "transactionQuery",
+            txId,
+            query,
+            params,
+            options,
+          );
+        },
+        exec: async (query, options) => {
+          return (await this.#rpc(
+            "transactionExec",
+            txId,
+            query,
+            options,
+          )) as any;
+        },
+        rollback: async () => {
+          await this.#rpc("transactionRollback", txId);
+        },
+        closed: false,
+      } as Transaction);
+    } catch (error) {
+      await this.#rpc("transactionRollback", txId);
+      throw error;
+    }
+    await this.#rpc("transactionCommit", txId);
+    return ret;
   }
 
+  /**
+   * Execute a postgres wire protocol message directly without wrapping the response.
+   * Only use if `execProtocol()` doesn't suite your needs.
+   *
+   * **Warning:** This bypasses PGlite's protocol wrappers that manage error/notice messages,
+   * transactions, and notification listeners. Only use if you need to bypass these wrappers and
+   * don't intend to use the above features.
+   *
+   * @param message The postgres wire protocol message to execute
+   * @returns The direct message data response produced by Postgres
+   */
   async execProtocolRaw(message: Uint8Array): Promise {
-    return this.#worker.execProtocolRaw(message);
+    await this.waitReady;
+    return (await this.#rpc("execProtocolRaw", message)) as Uint8Array;
   }
 
+  /**
+   * Execute a postgres wire protocol message
+   * @param message The postgres wire protocol message to execute
+   * @returns The result of the query
+   */
   async execProtocol(
     message: Uint8Array,
   ): Promise> {
-    return this.#worker.execProtocol(message);
+    await this.waitReady;
+    return (await this.#rpc("execProtocol", message)) as Array<
+      [BackendMessage, Uint8Array]
+    >;
   }
 
+  /**
+   * Listen for a notification
+   * @param channel The channel to listen on
+   * @param callback The callback to call when a notification is received
+   */
   async listen(
     channel: string,
     callback: (payload: string) => void,
   ): Promise<() => Promise> {
+    await this.waitReady;
     if (!this.#notifyListeners.has(channel)) {
       this.#notifyListeners.set(channel, new Set());
     }
@@ -111,10 +420,16 @@ export class PGliteWorker implements PGliteInterface {
     };
   }
 
+  /**
+   * Stop listening for a notification
+   * @param channel The channel to stop listening on
+   * @param callback The callback to remove
+   */
   async unlisten(
     channel: string,
     callback?: (payload: string) => void,
   ): Promise {
+    await this.waitReady;
     if (callback) {
       this.#notifyListeners.get(channel)?.delete(callback);
     } else {
@@ -126,6 +441,10 @@ export class PGliteWorker implements PGliteInterface {
     }
   }
 
+  /**
+   * Listen to notifications
+   * @param callback The callback to call when a notification is received
+   */
   onNotification(callback: (channel: string, payload: string) => void) {
     this.#globalNotifyListeners.add(callback);
     return () => {
@@ -133,11 +452,15 @@ export class PGliteWorker implements PGliteInterface {
     };
   }
 
+  /**
+   * Stop listening to notifications
+   * @param callback The callback to remove
+   */
   offNotification(callback: (channel: string, payload: string) => void) {
     this.#globalNotifyListeners.delete(callback);
   }
 
-  receiveNotification(channel: string, payload: string) {
+  #receiveNotification(channel: string, payload: string) {
     const listeners = this.#notifyListeners.get(channel);
     if (listeners) {
       for (const listener of listeners) {
@@ -149,7 +472,291 @@ export class PGliteWorker implements PGliteInterface {
     }
   }
 
-  async dumpDataDir() {
-    return this.#worker.dumpDataDir();
+  async dumpDataDir(): Promise {
+    return (await this.#rpc("dumpDataDir")) as File | Blob;
+  }
+
+  onLeaderChange(callback: () => void) {
+    this.#eventTarget.addEventListener("leader-change", callback);
+    return () => {
+      this.#eventTarget.removeEventListener("leader-change", callback);
+    };
+  }
+
+  offLeaderChange(callback: () => void) {
+    this.#eventTarget.removeEventListener("leader-change", callback);
+  }
+}
+
+export interface WorkerOptions {
+  init: (
+    options: Exclude,
+  ) => Promise;
+}
+
+export async function worker({ init }: WorkerOptions) {
+  // Send a message to the main thread to let it know we are here
+  postMessage({ type: "here" });
+
+  // Await the main thread to send us the options
+  const options = await new Promise>(
+    (resolve) => {
+      addEventListener(
+        "message",
+        (event) => {
+          if (event.data.type === "init") {
+            resolve(event.data.options);
+          }
+        },
+        { once: true },
+      );
+    },
+  );
+
+  // ID for this multi-tab worker - this is used to identify the group of workers
+  // that are trying to elect a leader for a shared PGlite instance.
+  // It defaults to the URL of the worker, and the dataDir if provided
+  // but can be overridden by the options.
+  const id = options.id ?? `${import.meta.url}:${options.dataDir ?? ""}`;
+
+  // Let the main thread know we are ready
+  postMessage({ type: "ready", id });
+
+  const electionLockId = `pglite-election-lock:${id}`;
+  const broadcastChannelId = `pglite-broadcast:${id}`;
+  const broadcastChannel = new BroadcastChannel(broadcastChannelId);
+  const connectedTabs = new Set();
+
+  // Await the main lock which is used to elect the leader
+  // We don't release this lock, its automatically released when the worker or
+  // tab is closed
+  await acquireLock(electionLockId);
+
+  // Now we are the leader, start the worker
+  const dbPromise = init(options);
+
+  // Start listening for messages from tabs
+  broadcastChannel.onmessage = async (event) => {
+    const msg = event.data;
+    switch (msg.type) {
+      case "tab-here":
+        // A new tab has joined,
+        connectTab(msg.id, await dbPromise, connectedTabs);
+        break;
+    }
+  };
+
+  // Notify the other tabs that we are the leader
+  broadcastChannel.postMessage({ type: "leader-here", id });
+
+  // Let the main thread know we are the leader
+  postMessage({ type: "leader-now" });
+
+  const db = await dbPromise;
+
+  // Listen for notifications and broadcast them to all tabs
+  db.onNotification((channel, payload) => {
+    broadcastChannel.postMessage({ type: "notify", channel, payload });
+  });
+}
+
+function connectTab(
+  tabId: string,
+  pg: PGliteInterface,
+  connectedTabs: Set,
+) {
+  if (connectedTabs.has(tabId)) {
+    return;
+  }
+  connectedTabs.add(tabId);
+  const tabChannelId = `pglite-tab:${tabId}`;
+  const tabCloseLockId = `pglite-tab-close:${tabId}`;
+  const tabChannel = new BroadcastChannel(tabChannelId);
+
+  // Use a tab close lock to unsubscribe the tab
+  navigator.locks.request(tabCloseLockId, () => {
+    return new Promise((resolve) => {
+      // The tab has been closed, unsubscribe the tab broadcast channel
+      tabChannel.close();
+      connectedTabs.delete(tabId);
+      resolve();
+    });
+  });
+
+  const api = makeWorkerApi(pg);
+
+  tabChannel.addEventListener("message", async (event) => {
+    const msg = event.data;
+    switch (msg.type) {
+      case "rpc-call":
+        const { callId, method, args } = msg as WorkerRpcCall;
+        try {
+          // @ts-ignore
+          const result = (await api[method](...args)) as WorkerRpcResult<
+            typeof method
+          >["result"];
+          tabChannel.postMessage({
+            type: "rpc-return",
+            callId,
+            result,
+          } satisfies WorkerRpcResult);
+        } catch (error) {
+          console.error(error);
+          tabChannel.postMessage({
+            type: "rpc-error",
+            callId,
+            error: { message: (error as Error).message },
+          } satisfies WorkerRpcError);
+        }
+        break;
+    }
+  });
+
+  // Send a message to the tab to let it know it's connected
+  tabChannel.postMessage({ type: "connected" });
+}
+
+function makeWorkerApi(db: PGliteInterface) {
+  const transactions = new Map<
+    string,
+    Promise<{
+      tx: Transaction;
+      resolve: () => void;
+      reject: (error: any) => void;
+    }>
+  >();
+
+  return {
+    async getDebugLevel() {
+      return db.debug;
+    },
+    async close() {
+      await db.close();
+    },
+    async query(query: string, params?: any[], options?: QueryOptions) {
+      return await db.query(query, params, options);
+    },
+    async exec(query: string, options?: QueryOptions) {
+      return await db.exec(query, options);
+    },
+    async transactionStart() {
+      const txId = uuid();
+      const { promise: txPromise, resolve: resolveTxPromise } = makePromise<{
+        tx: Transaction;
+        resolve: () => void;
+        reject: (error: any) => void;
+      }>();
+      transactions.set(txId, txPromise);
+      db.transaction((newTx) => {
+        return new Promise((resolveTx, rejectTx) => {
+          resolveTxPromise({
+            tx: newTx,
+            resolve: resolveTx,
+            reject: rejectTx,
+          });
+        });
+      });
+      return txId;
+    },
+    async transactionCommit(id: string) {
+      if (!transactions.has(id)) {
+        throw new Error("No transaction");
+      }
+      (await transactions.get(id)!).resolve();
+      transactions.delete(id);
+    },
+    async transactionQuery(
+      id: string,
+      query: string,
+      params?: any[],
+      options?: QueryOptions,
+    ) {
+      if (!transactions.has(id)) {
+        throw new Error("No transaction");
+      }
+      const tx = (await transactions.get(id)!).tx;
+      return await tx.query(query, params, options);
+    },
+    async transactionExec(id: string, query: string, options?: QueryOptions) {
+      if (!transactions.has(id)) {
+        throw new Error("No transaction");
+      }
+      const tx = (await transactions.get(id)!).tx;
+      return tx.exec(query, options);
+    },
+    async transactionRollback(id: string) {
+      if (!transactions.has(id)) {
+        throw new Error("No transaction");
+      }
+      const tx = await transactions.get(id)!;
+      await tx.tx.rollback();
+      tx.reject(new Error("Transaction rolled back"));
+      transactions.delete(id);
+    },
+    async execProtocol(message: Uint8Array) {
+      return await db.execProtocol(message);
+    },
+    async execProtocolRaw(message: Uint8Array) {
+      return await db.execProtocolRaw(message);
+    },
+    async dumpDataDir() {
+      return await db.dumpDataDir();
+    },
+  };
+}
+
+export class LeaderChangedError extends Error {
+  constructor() {
+    super("Leader changed, pending operation in indeterminate state");
   }
 }
+
+async function acquireLock(lockId: string) {
+  let release;
+  await new Promise((resolve) => {
+    navigator.locks.request(lockId, () => {
+      return new Promise((releaseCallback) => {
+        release = releaseCallback;
+        resolve();
+      });
+    });
+  });
+  return release;
+}
+
+function makePromise() {
+  let resolve: (value: T) => void;
+  let reject: (error: any) => void;
+  const promise = new Promise((res, rej) => {
+    resolve = res;
+    reject = rej;
+  });
+  return { promise, resolve: resolve!, reject: reject! };
+}
+
+type WorkerApi = ReturnType;
+
+type WorkerRpcMethod = keyof WorkerApi;
+
+type WorkerRpcCall = {
+  type: "rpc-call";
+  callId: string;
+  method: Method;
+  args: Parameters;
+};
+
+type WorkerRpcResult = {
+  type: "rpc-return";
+  callId: string;
+  result: ReturnType;
+};
+
+type WorkerRpcError = {
+  type: "rpc-error";
+  callId: string;
+  error: any;
+};
+
+type WorkerRpcResponse =
+  | WorkerRpcResult
+  | WorkerRpcError;
diff --git a/packages/pglite/src/worker/process.ts b/packages/pglite/src/worker/process.ts
deleted file mode 100644
index eac2df57..00000000
--- a/packages/pglite/src/worker/process.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import * as Comlink from "comlink";
-import { PGlite } from "../index.js";
-import type { PGliteOptions, QueryOptions } from "../interface.js";
-
-let db: PGlite;
-
-const worker = {
-  async init(
-    dataDir?: string,
-    options?: PGliteOptions,
-    onNotification?: (channel: string, payload: string) => void,
-  ) {
-    db = new PGlite(dataDir, options);
-    await db.waitReady;
-    if (onNotification) {
-      db.onNotification(onNotification);
-    }
-    return true;
-  },
-  async close() {
-    await db.close();
-  },
-  async query(query: string, params?: any[], options?: QueryOptions) {
-    return await db.query(query, params, options);
-  },
-  async exec(query: string, options?: QueryOptions) {
-    return await db.exec(query, options);
-  },
-  async transaction(callback: (tx: any) => Promise) {
-    return await db.transaction((tx) => {
-      return callback(Comlink.proxy(tx));
-    });
-  },
-  async execProtocolRaw(message: Uint8Array) {
-    return await db.execProtocolRaw(message);
-  },
-  async execProtocol(message: Uint8Array) {
-    return await db.execProtocol(message);
-  },
-  async dumpDataDir() {
-    const file = await db.dumpDataDir();
-    return Comlink.transfer(file, [await file.arrayBuffer()]);
-  },
-};
-
-Comlink.expose(worker);
-
-export type Worker = typeof worker;
diff --git a/packages/pglite/tests/contrib/adminpack.test.js b/packages/pglite/tests/contrib/adminpack.test.js
new file mode 100644
index 00000000..c14911dd
--- /dev/null
+++ b/packages/pglite/tests/contrib/adminpack.test.js
@@ -0,0 +1,53 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { adminpack } from "../../dist/contrib/adminpack.js";
+
+test("adminpack", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      adminpack,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS adminpack;");
+
+  // Write a file to the virtual file system
+  const res = await pg.query("SELECT pg_catalog.pg_file_write('/test.txt', 'test', false);");
+  t.deepEqual(res.rows, [
+    {
+      "pg_file_write": 4
+    }
+  ]);
+
+  // Read the file from the virtual file system to verify the content
+  const stream = pg.Module.FS.open("/test.txt", "r");
+  const buffer = new Uint8Array(4);
+  pg.Module.FS.read(stream, buffer, 0, 4, 0);
+  pg.Module.FS.close(stream);
+  const text = new TextDecoder().decode(buffer);
+  t.is(text, "test");
+
+  // Rename the file
+  const res2 = await pg.query("SELECT pg_catalog.pg_file_rename('/test.txt', '/test2.txt');");
+  t.deepEqual(res2.rows, [
+    {
+      "pg_file_rename": true
+    }
+  ]);
+  const stats = pg.Module.FS.lstat("/test2.txt");
+  t.is(stats.size, 4);
+
+  // Remove the file
+  const res3 = await pg.query("SELECT pg_catalog.pg_file_unlink('/test2.txt');");
+  t.deepEqual(res3.rows, [
+    {
+      "pg_file_unlink": true
+    }
+  ]);
+  // should throw an error
+  try {
+    pg.Module.FS.lstat("/test2.txt")
+  } catch (e) {
+    t.is(e.errno, 44);
+  }
+});
diff --git a/packages/pglite/tests/contrib/amcheck.test.js b/packages/pglite/tests/contrib/amcheck.test.js
new file mode 100644
index 00000000..59f98320
--- /dev/null
+++ b/packages/pglite/tests/contrib/amcheck.test.js
@@ -0,0 +1,76 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { amcheck } from "../../dist/contrib/amcheck.js";
+
+test("amcheck", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      amcheck,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS amcheck;");
+
+  // Example query from https://www.postgresql.org/docs/current/amcheck.html
+  const res = await pg.query(`
+    SELECT bt_index_check(index => c.oid, heapallindexed => i.indisunique),
+               c.relname,
+               c.relpages
+    FROM pg_index i
+    JOIN pg_opclass op ON i.indclass[0] = op.oid
+    JOIN pg_am am ON op.opcmethod = am.oid
+    JOIN pg_class c ON i.indexrelid = c.oid
+    JOIN pg_namespace n ON c.relnamespace = n.oid
+    WHERE am.amname = 'btree' AND n.nspname = 'pg_catalog'
+    -- Don't check temp tables, which may be from another session:
+    AND c.relpersistence != 't'
+    -- Function may throw an error when this is omitted:
+    AND c.relkind = 'i' AND i.indisready AND i.indisvalid
+    ORDER BY c.relpages DESC LIMIT 10;
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      bt_index_check: "",
+      relname: "pg_proc_proname_args_nsp_index",
+      relpages: 32,
+    },
+    {
+      bt_index_check: "",
+      relname: "pg_description_o_c_o_index",
+      relpages: 24,
+    },
+    {
+      bt_index_check: "",
+      relname: "pg_attribute_relid_attnam_index",
+      relpages: 16,
+    },
+    { bt_index_check: "", relname: "pg_proc_oid_index", relpages: 11 },
+    {
+      bt_index_check: "",
+      relname: "pg_attribute_relid_attnum_index",
+      relpages: 11,
+    },
+    {
+      bt_index_check: "",
+      relname: "pg_depend_depender_index",
+      relpages: 9,
+    },
+    {
+      bt_index_check: "",
+      relname: "pg_depend_reference_index",
+      relpages: 8,
+    },
+    { bt_index_check: "", relname: "pg_amop_opr_fam_index", relpages: 6 },
+    {
+      bt_index_check: "",
+      relname: "pg_amop_fam_strat_index",
+      relpages: 6,
+    },
+    {
+      bt_index_check: "",
+      relname: "pg_operator_oprname_l_r_n_index",
+      relpages: 6,
+    },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/auto_explain.test.js b/packages/pglite/tests/contrib/auto_explain.test.js
new file mode 100644
index 00000000..6eb414ee
--- /dev/null
+++ b/packages/pglite/tests/contrib/auto_explain.test.js
@@ -0,0 +1,33 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { auto_explain } from "../../dist/contrib/auto_explain.js";
+
+test("auto_explain", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      auto_explain,
+    },
+  });
+
+  await pg.exec(`
+    LOAD 'auto_explain';
+    SET auto_explain.log_min_duration = '0';
+    SET auto_explain.log_analyze = 'true';
+  `);
+
+  const notices = [];
+
+  const res = await pg.query(`
+    SELECT count(*)
+    FROM pg_class, pg_index
+    WHERE oid = indrelid AND indisunique;
+  `, [], {
+    onNotice: (msg) => {
+      notices.push(msg);
+    },
+  });
+
+  const explainNotice = notices.find((msg) => msg.routine === "explain_ExecutorEnd");
+
+  t.truthy(explainNotice);
+});
diff --git a/packages/pglite/tests/contrib/bloom.test.js b/packages/pglite/tests/contrib/bloom.test.js
new file mode 100644
index 00000000..8b3b35d5
--- /dev/null
+++ b/packages/pglite/tests/contrib/bloom.test.js
@@ -0,0 +1,50 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { bloom } from "../../dist/contrib/bloom.js";
+
+test("bloom", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      bloom,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS bloom;");
+
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      name TEXT
+    );
+    CREATE INDEX IF NOT EXISTS test_name_bloom_idx ON test USING bloom (name);
+  `);
+
+  await pg.exec("INSERT INTO test (name) VALUES ('test1');");
+  await pg.exec("INSERT INTO test (name) VALUES ('test2');");
+  await pg.exec("INSERT INTO test (name) VALUES ('test3');");
+
+  const res = await pg.query(`
+    SELECT
+      name
+    FROM test
+    WHERE name = 'test1';
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      "name": "test1"
+    },
+  ]);
+
+  const res2 = await pg.query(`
+    EXPLAIN ANALYZE
+    SELECT
+      name
+    FROM test
+    WHERE name = 'test1';
+  `);
+
+  // check that `test_name_bloom_idx` is in the plan
+  const match = res2.rows.filter((row) => row["QUERY PLAN"].includes("test_name_bloom_idx"));
+  t.true(match.length > 0);
+});
diff --git a/packages/pglite/tests/contrib/btree_gin.test.js b/packages/pglite/tests/contrib/btree_gin.test.js
new file mode 100644
index 00000000..01914489
--- /dev/null
+++ b/packages/pglite/tests/contrib/btree_gin.test.js
@@ -0,0 +1,50 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { btree_gin } from "../../dist/contrib/btree_gin.js";
+
+test("btree_gin", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      btree_gin,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS btree_gin;");
+
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      number int4
+    );
+    CREATE INDEX IF NOT EXISTS test_number_btree_gin_idx ON test USING GIN (number);
+  `);
+
+  await pg.exec("INSERT INTO test (number) VALUES (1);");
+  await pg.exec("INSERT INTO test (number) VALUES (2);");
+  await pg.exec("INSERT INTO test (number) VALUES (3);");
+
+  const res = await pg.query(`
+    SELECT
+      number
+    FROM test
+    WHERE number = 1;
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      "number": 1,
+    },
+  ]);
+
+  const res2 = await pg.query(`
+    EXPLAIN ANALYZE
+    SELECT
+      number
+    FROM test
+    WHERE number = 1;
+  `);
+
+  // check that `test_number_btree_gin_idx` is in the plan
+  const match = res2.rows.filter((row) => row["QUERY PLAN"].includes("test_number_btree_gin_idx"));
+  t.true(match.length > 0);
+});
diff --git a/packages/pglite/tests/contrib/btree_gist.test.js b/packages/pglite/tests/contrib/btree_gist.test.js
new file mode 100644
index 00000000..0dd4137c
--- /dev/null
+++ b/packages/pglite/tests/contrib/btree_gist.test.js
@@ -0,0 +1,50 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { btree_gist } from "../../dist/contrib/btree_gist.js";
+
+test("btree_gist", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      btree_gist,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS btree_gist;");
+
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      number int4
+    );
+    CREATE INDEX IF NOT EXISTS test_number_btree_gist_idx ON test USING GIST (number);
+  `);
+  
+  await pg.exec("INSERT INTO test (number) VALUES (1);");
+  await pg.exec("INSERT INTO test (number) VALUES (2);");
+  await pg.exec("INSERT INTO test (number) VALUES (3);");
+
+  const res = await pg.query(`
+    SELECT
+      number
+    FROM test
+    WHERE number = 1;
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      "number": 1,
+    },
+  ]);
+
+  const res2 = await pg.query(`
+    EXPLAIN ANALYZE
+    SELECT
+      number
+    FROM test
+    WHERE number = 1;
+  `);
+
+  // check that `test_number_btree_gist_idx` is in the plan
+  const match = res2.rows.filter((row) => row["QUERY PLAN"].includes("test_number_btree_gist_idx"));
+  t.true(match.length > 0);
+});
diff --git a/packages/pglite/tests/contrib/citext.test.js b/packages/pglite/tests/contrib/citext.test.js
new file mode 100644
index 00000000..7e4e7931
--- /dev/null
+++ b/packages/pglite/tests/contrib/citext.test.js
@@ -0,0 +1,37 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { citext } from "../../dist/contrib/citext.js";
+
+test("citext", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      citext,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS citext;");
+
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      name CITEXT
+    );
+  `);
+
+  await pg.exec("INSERT INTO test (name) VALUES ('tEsT1');");
+  await pg.exec("INSERT INTO test (name) VALUES ('TeSt2');");
+  await pg.exec("INSERT INTO test (name) VALUES ('TEST3');");
+
+  const res = await pg.query(`
+    SELECT
+      name
+    FROM test
+    WHERE name = 'test1';
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      "name": "tEsT1"
+    },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/cube.test.js b/packages/pglite/tests/contrib/cube.test.js
new file mode 100644
index 00000000..b08b1c36
--- /dev/null
+++ b/packages/pglite/tests/contrib/cube.test.js
@@ -0,0 +1,37 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { cube } from "../../dist/contrib/cube.js";
+
+test("cube", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      cube,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS cube;");
+
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      point CUBE
+    );
+  `);
+
+  await pg.exec("INSERT INTO test (point) VALUES ('(1, 2, 3)');");
+  await pg.exec("INSERT INTO test (point) VALUES ('(4, 5, 6)');");
+  await pg.exec("INSERT INTO test (point) VALUES ('(7, 8, 9)');");
+
+  const res = await pg.query(`
+    SELECT
+      point,
+      point <-> cube(array[1, 2, 3]) AS distance
+    FROM test;
+  `);
+
+  t.deepEqual(res.rows, [
+    { point: "(1, 2, 3)", distance: 0 },
+    { point: "(4, 5, 6)", distance: 5.196152422706632 },
+    { point: "(7, 8, 9)", distance: 10.392304845413264 },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/earthdistance.test.js b/packages/pglite/tests/contrib/earthdistance.test.js
new file mode 100644
index 00000000..1e34cf3c
--- /dev/null
+++ b/packages/pglite/tests/contrib/earthdistance.test.js
@@ -0,0 +1,54 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { cube } from "../../dist/contrib/cube.js";
+import { earthdistance } from "../../dist/contrib/earthdistance.js";
+
+test("earthdistance", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      cube,
+      earthdistance,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS cube;");
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS earthdistance;");
+
+  await pg.exec(`
+    CREATE TABLE locations (
+      id SERIAL PRIMARY KEY,
+      name VARCHAR(100),
+      latitude DOUBLE PRECISION,
+      longitude DOUBLE PRECISION
+    );
+  `);
+
+  await pg.exec(`
+    INSERT INTO locations (name, latitude, longitude)
+    VALUES
+      ('Location A', 40.7128, -74.0060),  -- New York City (nearby point)
+      ('Location B', 40.730610, -73.935242),  -- Another point in NYC
+      ('Location C', 34.052235, -118.243683),  -- Los Angeles (far away)
+      ('Location D', 40.758896, -73.985130),  -- Times Square, NYC
+      ('Location E', 51.507351, -0.127758);  -- London (far away)
+  `);
+
+  const res = await pg.query(`
+    SELECT
+      name,
+      earth_distance(
+        ll_to_earth(40.7128, -74.0060),
+        ll_to_earth(latitude, longitude)
+      ) AS distance
+    FROM locations
+    ORDER BY distance;
+  `);
+
+  t.deepEqual(res.rows, [
+    { name: "Location A", distance: 0 },
+    { name: "Location D", distance: 5424.971028170555 },
+    { name: "Location B", distance: 6290.327117342975 },
+    { name: "Location C", distance: 3940171.3340000752 },
+    { name: "Location E", distance: 5576493.70395964 },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/fuzzystrmatch.test.js b/packages/pglite/tests/contrib/fuzzystrmatch.test.js
new file mode 100644
index 00000000..4da1f27d
--- /dev/null
+++ b/packages/pglite/tests/contrib/fuzzystrmatch.test.js
@@ -0,0 +1,31 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { fuzzystrmatch } from "../../dist/contrib/fuzzystrmatch.js";
+
+test("fuzzystrmatch", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      fuzzystrmatch,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;");
+
+  const res = await pg.query(`
+    SELECT
+      levenshtein('kitten', 'sitting') AS distance;
+  `);
+
+  t.deepEqual(res.rows, [
+    { distance: 3 },
+  ]);
+
+  const res2 = await pg.query(`
+    SELECT
+      soundex('kitten') AS soundex;
+  `);
+
+  t.deepEqual(res2.rows, [
+    { soundex: "K350" },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/hstore.test.js b/packages/pglite/tests/contrib/hstore.test.js
new file mode 100644
index 00000000..94dc1cff
--- /dev/null
+++ b/packages/pglite/tests/contrib/hstore.test.js
@@ -0,0 +1,39 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { hstore } from "../../dist/contrib/hstore.js";
+
+test("hstore", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      hstore,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS hstore;");
+
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      data HSTORE
+    );
+  `);
+
+  await pg.exec("INSERT INTO test (data) VALUES ('\"name\" => \"test1\"');");
+  await pg.exec("INSERT INTO test (data) VALUES ('\"name\" => \"test2\"');");
+  await pg.exec("INSERT INTO test (data) VALUES ('\"name\" => \"test3\"');");
+
+  const res = await pg.query(`
+    SELECT
+      data::JSONB
+    FROM test
+    WHERE data->'name' = 'test1';
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      "data": {
+        "name": "test1",
+      },
+    },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/isn.test.js b/packages/pglite/tests/contrib/isn.test.js
new file mode 100644
index 00000000..b0020b5e
--- /dev/null
+++ b/packages/pglite/tests/contrib/isn.test.js
@@ -0,0 +1,46 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { isn } from "../../dist/contrib/isn.js";
+
+test("bloom", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      isn,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS isn;");
+
+  const ret1 = await pg.query("SELECT isbn('978-0-393-04002-9');");
+  t.deepEqual(ret1.rows, [
+    {
+      "isbn": "0-393-04002-X"
+    },
+  ]);
+
+  const ret2 = await pg.query("SELECT isbn13('0901690546');");
+  t.deepEqual(ret2.rows, [
+    {
+      "isbn13": "978-0-901690-54-8"
+    },
+  ]);
+
+  const ret3 = await pg.query("SELECT issn('1436-4522');");
+  t.deepEqual(ret3.rows, [
+    {
+      "issn": "1436-4522"
+    },
+  ]);
+
+  await pg.exec(`
+    CREATE TABLE test (id isbn);
+    INSERT INTO test VALUES('9780393040029');
+  `)
+
+  const ret4 = await pg.query("SELECT * FROM test;");
+  t.deepEqual(ret4.rows, [
+    {
+      "id": "0-393-04002-X"
+    },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/lo.test.js b/packages/pglite/tests/contrib/lo.test.js
new file mode 100644
index 00000000..98083e3e
--- /dev/null
+++ b/packages/pglite/tests/contrib/lo.test.js
@@ -0,0 +1,43 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { lo } from "../../dist/contrib/lo.js";
+
+test("lo", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      lo,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS lo;");
+
+  await pg.exec(`
+    CREATE TABLE test (id SERIAL PRIMARY KEY, data OID);
+  `);
+
+  await pg.exec(`
+    CREATE TRIGGER test_data_lo BEFORE UPDATE OR DELETE ON test
+    FOR EACH ROW EXECUTE FUNCTION lo_manage(data);
+  `);
+
+  const text = "hello world";
+  const blob = new Blob([text], { type: "text/plain" });
+
+  await pg.query(`
+    INSERT INTO test (data) VALUES (lo_import('/dev/blob'));
+  `, [], {
+    blob,
+  });
+
+  const res = await pg.query(`
+    SELECT lo_export(data, '/dev/blob') AS data FROM test;
+  `);
+
+  const data = res.blob;
+  const asText = await data.text();
+  t.is(asText, text);
+
+  await pg.query(`
+    DELETE FROM test;
+  `);
+});
diff --git a/packages/pglite/tests/contrib/ltree.test.js b/packages/pglite/tests/contrib/ltree.test.js
new file mode 100644
index 00000000..d1ff6391
--- /dev/null
+++ b/packages/pglite/tests/contrib/ltree.test.js
@@ -0,0 +1,43 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { ltree } from "../../dist/contrib/ltree.js";
+
+test("ltree", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      ltree,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS ltree;");
+
+  await pg.exec(`
+    CREATE TABLE test (path ltree);
+    INSERT INTO test VALUES ('Top');
+    INSERT INTO test VALUES ('Top.Science');
+    INSERT INTO test VALUES ('Top.Science.Astronomy');
+    INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
+    INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
+    INSERT INTO test VALUES ('Top.Hobbies');
+    INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
+    INSERT INTO test VALUES ('Top.Collections');
+    INSERT INTO test VALUES ('Top.Collections.Pictures');
+    INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
+    INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
+    INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
+    INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
+    CREATE INDEX path_gist_idx ON test USING GIST (path);
+    CREATE INDEX path_idx ON test USING BTREE (path);
+  `);
+
+  const ret = await pg.query(`
+    SELECT path FROM test WHERE path <@ 'Top.Science';
+  `);
+
+  t.deepEqual(ret.rows, [
+    { path: 'Top.Science' },
+    { path: 'Top.Science.Astronomy' },
+    { path: 'Top.Science.Astronomy.Astrophysics' },
+    { path: 'Top.Science.Astronomy.Cosmology' }
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/pg_trgm.test.js b/packages/pglite/tests/contrib/pg_trgm.test.js
new file mode 100644
index 00000000..4b991c94
--- /dev/null
+++ b/packages/pglite/tests/contrib/pg_trgm.test.js
@@ -0,0 +1,127 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { pg_trgm } from "../../dist/contrib/pg_trgm.js";
+
+test("pg_trgm gin", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      pg_trgm,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS pg_trgm;");
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      name TEXT
+    );
+    CREATE INDEX IF NOT EXISTS test_name_trgm_idx ON test USING gin (name gin_trgm_ops);
+  `);
+  await pg.exec("INSERT INTO test (name) VALUES ('test1');");
+  await pg.exec("INSERT INTO test (name) VALUES ('test2');");
+  await pg.exec("INSERT INTO test (name) VALUES ('text3');");
+
+  const res = await pg.query(`
+    SELECT
+      name,
+      name % 'test' AS similarity,
+      name <-> 'test' AS distance
+    FROM test;
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      "name": "test1",
+      "similarity": true,
+      "distance": 0.4285714
+    },
+    {
+      "name": "test2",
+      "similarity": true,
+      "distance": 0.4285714
+    },
+    {
+      "name": "text3",
+      "similarity": false,
+      "distance": 0.7777778
+    }
+  ]);
+
+  const res2 = await pg.query(`
+    SELECT
+      name
+    FROM test
+    WHERE name % 'test';
+  `);
+
+  t.deepEqual(res2.rows, [
+    {
+      "name": "test1"
+    },
+    {
+      "name": "test2"
+    }
+  ]);
+});
+
+test("pg_trgm gist", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      pg_trgm,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS pg_trgm;");
+  await pg.exec(`
+    CREATE TABLE IF NOT EXISTS test (
+      id SERIAL PRIMARY KEY,
+      name TEXT
+    );
+    CREATE INDEX IF NOT EXISTS test_name_trgm_idx ON test USING gist (name gist_trgm_ops);
+  `);
+  await pg.exec("INSERT INTO test (name) VALUES ('test1');");
+  await pg.exec("INSERT INTO test (name) VALUES ('test2');");
+  await pg.exec("INSERT INTO test (name) VALUES ('text3');");
+
+  const res = await pg.query(`
+    SELECT
+      name,
+      name % 'test' AS similarity,
+      name <-> 'test' AS distance
+    FROM test;
+  `);
+
+  t.deepEqual(res.rows, [
+    {
+      "name": "test1",
+      "similarity": true,
+      "distance": 0.4285714
+    },
+    {
+      "name": "test2",
+      "similarity": true,
+      "distance": 0.4285714
+    },
+    {
+      "name": "text3",
+      "similarity": false,
+      "distance": 0.7777778
+    }
+  ]);
+
+  const res2 = await pg.query(`
+    SELECT
+      name
+    FROM test
+    WHERE name % 'test';
+  `);
+
+  t.deepEqual(res2.rows, [
+    {
+      "name": "test1"
+    },
+    {
+      "name": "test2"
+    }
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/seg.test.js b/packages/pglite/tests/contrib/seg.test.js
new file mode 100644
index 00000000..191ec911
--- /dev/null
+++ b/packages/pglite/tests/contrib/seg.test.js
@@ -0,0 +1,19 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { seg } from "../../dist/contrib/seg.js";
+
+test("seg", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      seg,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS seg;");
+
+  const ret = await pg.query(`SELECT '6.25 .. 6.50'::seg AS "pH"`);
+  t.deepEqual(ret.rows, [{ pH: "6.25 .. 6.50" }]);
+
+  const ret2 = await pg.query(`SELECT '7(+-)1'::seg AS "set"`);
+  t.deepEqual(ret2.rows, [{ set: "6 .. 8" }]);
+});
diff --git a/packages/pglite/tests/contrib/tablefunc.test.js b/packages/pglite/tests/contrib/tablefunc.test.js
new file mode 100644
index 00000000..ab776c53
--- /dev/null
+++ b/packages/pglite/tests/contrib/tablefunc.test.js
@@ -0,0 +1,53 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { tablefunc } from "../../dist/contrib/tablefunc.js";
+
+test("tablefunc", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      tablefunc,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS tablefunc;");
+
+  const ret = await pg.query(`SELECT * FROM normal_rand(10, 5, 3)`);
+  t.deepEqual(ret.rows.length, 10);
+
+  await pg.exec(`
+    CREATE TABLE ct(id SERIAL, rowid TEXT, attribute TEXT, value TEXT);
+    INSERT INTO ct(rowid, attribute, value) VALUES('test1','att1','val1');
+    INSERT INTO ct(rowid, attribute, value) VALUES('test1','att2','val2');
+    INSERT INTO ct(rowid, attribute, value) VALUES('test1','att3','val3');
+    INSERT INTO ct(rowid, attribute, value) VALUES('test1','att4','val4');
+    INSERT INTO ct(rowid, attribute, value) VALUES('test2','att1','val5');
+    INSERT INTO ct(rowid, attribute, value) VALUES('test2','att2','val6');
+    INSERT INTO ct(rowid, attribute, value) VALUES('test2','att3','val7');
+    INSERT INTO ct(rowid, attribute, value) VALUES('test2','att4','val8');
+  `);
+
+  const ret2 = await pg.query(`
+    SELECT *
+    FROM crosstab(
+      'select rowid, attribute, value
+      from ct
+      where attribute = ''att2'' or attribute = ''att3''
+      order by 1,2')
+    AS ct(row_name text, category_1 text, category_2 text, category_3 text);
+  `);
+
+  t.deepEqual(ret2.rows, [
+    {
+      row_name: "test1",
+      category_1: "val2",
+      category_2: "val3",
+      category_3: null,
+    },
+    {
+      row_name: "test2",
+      category_1: "val6",
+      category_2: "val7",
+      category_3: null,
+    },
+  ]);
+});
diff --git a/packages/pglite/tests/contrib/tcn.test.js b/packages/pglite/tests/contrib/tcn.test.js
new file mode 100644
index 00000000..20b13d4a
--- /dev/null
+++ b/packages/pglite/tests/contrib/tcn.test.js
@@ -0,0 +1,30 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { tcn } from "../../dist/contrib/tcn.js";
+
+test("tcn", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      tcn,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS tcn;");
+
+  await pg.exec(`
+    CREATE TABLE test (
+      id SERIAL PRIMARY KEY,
+      name TEXT
+    );
+    CREATE TRIGGER test_tcn
+    AFTER INSERT OR UPDATE OR DELETE ON test
+    FOR EACH ROW
+    EXECUTE FUNCTION triggered_change_notification();
+  `);
+
+  pg.listen("tcn", (payload) => {
+    t.is(payload, `"test",I,"id"='1'`);
+  });
+
+  await pg.exec("INSERT INTO test (name) VALUES ('test1');");
+});
diff --git a/packages/pglite/tests/contrib/tsm_system_rows.test.js b/packages/pglite/tests/contrib/tsm_system_rows.test.js
new file mode 100644
index 00000000..1056a0fc
--- /dev/null
+++ b/packages/pglite/tests/contrib/tsm_system_rows.test.js
@@ -0,0 +1,36 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { tsm_system_rows } from "../../dist/contrib/tsm_system_rows.js";
+
+test("tsm_system_rows", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      tsm_system_rows,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS tsm_system_rows;");
+
+  // crate test table with 10 rows
+  await pg.exec(`
+    CREATE TABLE test (
+      id SERIAL PRIMARY KEY,
+      name TEXT
+    );
+  `);
+  
+  await pg.exec(`
+    INSERT INTO test (name)
+    SELECT 'test' || i
+    FROM generate_series(1, 10) AS i;
+  `);
+
+  // sample 5 rows
+  const res = await pg.query(`
+    SELECT *
+    FROM test
+    TABLESAMPLE SYSTEM_ROWS(5);
+  `);
+
+  t.is(res.rows.length, 5);
+});
diff --git a/packages/pglite/tests/contrib/tsm_system_time.test.js b/packages/pglite/tests/contrib/tsm_system_time.test.js
new file mode 100644
index 00000000..c04535d3
--- /dev/null
+++ b/packages/pglite/tests/contrib/tsm_system_time.test.js
@@ -0,0 +1,36 @@
+import test from "ava";
+import { PGlite } from "../../dist/index.js";
+import { tsm_system_time } from "../../dist/contrib/tsm_system_time.js";
+
+test("tsm_system_time", async (t) => {
+  const pg = new PGlite({
+    extensions: {
+      tsm_system_time,
+    },
+  });
+
+  await pg.exec("CREATE EXTENSION IF NOT EXISTS tsm_system_time;");
+
+  // crate test table with 10 rows
+  await pg.exec(`
+    CREATE TABLE test (
+      id SERIAL PRIMARY KEY,
+      name TEXT
+    );
+  `);
+  
+  await pg.exec(`
+    INSERT INTO test (name)
+    SELECT 'test' || i
+    FROM generate_series(1, 10) AS i;
+  `);
+
+  // sample 5 rows
+  const res = await pg.query(`
+    SELECT *
+    FROM test
+    TABLESAMPLE SYSTEM_TIME(50);
+  `);
+
+  t.is(res.rows.length, 10);
+});
diff --git a/packages/pglite/tests/largeobjects.test.js b/packages/pglite/tests/largeobjects.test.js
new file mode 100644
index 00000000..d542513e
--- /dev/null
+++ b/packages/pglite/tests/largeobjects.test.js
@@ -0,0 +1,27 @@
+import test from "ava";
+import { PGlite } from "../dist/index.js";
+
+test("large objects", async (t) => {
+  const pg = new PGlite();
+
+  const text = "hello world";
+  const blob = new Blob([text], { type: "text/plain" });
+
+  await pg.exec(`
+    CREATE TABLE test (id SERIAL PRIMARY KEY, data OID);
+  `);
+
+  await pg.query(`
+    INSERT INTO test (data) VALUES (lo_import('/dev/blob'));
+  `, [], {
+    blob,
+  });
+
+  const res = await pg.query(`
+    SELECT lo_export(data, '/dev/blob') AS data FROM test;
+  `);
+
+  const data = res.blob;
+  const asText = await data.text();
+  t.is(asText, text);
+});
diff --git a/packages/pglite/tests/targets/base.js b/packages/pglite/tests/targets/base.js
index 63b05b22..7239c005 100644
--- a/packages/pglite/tests/targets/base.js
+++ b/packages/pglite/tests/targets/base.js
@@ -3,9 +3,12 @@ import playwright from "playwright";
 
 const wsPort = process.env.WS_PORT || 3334;
 
+const useWorkerForBbFilename = ["opfs-ahp://base"];
+
 export function tests(env, dbFilename, target) {
   let browser;
   let evaluate;
+  let context;
   let page;
   let db;
 
@@ -25,18 +28,41 @@ export function tests(env, dbFilename, target) {
     if (env === "node") {
       evaluate = async (fn) => fn();
     } else {
-      const context = await browser.newContext();
+      context = await browser.newContext();
       page = await context.newPage();
       await page.goto(`http://localhost:${wsPort}/tests/blank.html`);
       page.evaluate(`window.dbFilename = "${dbFilename}";`);
-      evaluate = async (fn) => await page.evaluate(fn);
+      page.evaluate(`window.useWorkerForBbFilename = ${JSON.stringify(useWorkerForBbFilename)};`);
+      page.on("console", (msg) => {
+        console.log(msg);
+      });
+      evaluate = async (fn) => {
+        try {
+          return await page.evaluate(fn);
+        } catch (e) {
+          console.error(e);
+          throw e;
+        }
+      };
     }
   });
 
   test.serial(`targets ${target} basic`, async (t) => {
     const res = await evaluate(async () => {
-      const { PGlite } = await import("../../dist/index.js");
-      db = new PGlite(dbFilename);
+      if (useWorkerForBbFilename.includes(dbFilename)) {
+        const { PGliteWorker } = await import("../../dist/worker/index.js");
+        db = new PGliteWorker(
+          new Worker("/tests/targets/worker.js", {
+            type: "module",
+          }),
+          {
+            dataDir: dbFilename,
+          }
+        );
+      } else {
+        const { PGlite } = await import("../../dist/index.js");
+        db = new PGlite(dbFilename);
+      }
       await db.waitReady;
       await db.query(`
           CREATE TABLE IF NOT EXISTS test (
@@ -114,10 +140,24 @@ export function tests(env, dbFilename, target) {
   test.serial(`targets ${target} persisted`, async (t) => {
     await page?.reload(); // Refresh the page
     page?.evaluate(`window.dbFilename = "${dbFilename}";`);
+    page?.evaluate(`window.useWorkerForBbFilename = ${JSON.stringify(useWorkerForBbFilename)};`);
 
     const res = await evaluate(async () => {
-      const { PGlite } = await import("../../dist/index.js");
-      const db = new PGlite(dbFilename);
+      let db;
+      if (useWorkerForBbFilename.includes(dbFilename)) {
+        const { PGliteWorker } = await import("../../dist/worker/index.js");
+        db = new PGliteWorker(
+          new Worker("/tests/targets/worker.js", {
+            type: "module",
+          }),
+          {
+            dataDir: dbFilename,
+          }
+        );
+      } else {
+        const { PGlite } = await import("../../dist/index.js");
+        db = new PGlite(dbFilename);
+      }
       await db.waitReady;
       const res = await db.query(`
           SELECT * FROM test;
@@ -149,4 +189,242 @@ export function tests(env, dbFilename, target) {
       ],
     });
   });
+
+  if (env === "node") {
+    // Skip the rest of the tests for node as they are browser specific
+    return;
+  }
+
+  test.serial(`targets ${target} worker live query`, async (t) => {
+    const page2 = await context.newPage();
+    await page2.goto(`http://localhost:${wsPort}/tests/blank.html`);
+    page2.evaluate(`window.dbFilename = "${dbFilename}";`);
+    page.on("console", (msg) => {
+      console.log(msg);
+    });
+
+    const res2Prom = page2.evaluate(async () => {
+      const { live } = await import("../../dist/live/index.js");
+      const { PGliteWorker } = await import("../../dist/worker/index.js");
+
+      let db;
+      db = new PGliteWorker(
+        new Worker("/tests/targets/worker.js", {
+          type: "module",
+        }),
+        {
+          dataDir: window.dbFilename,
+          extensions: { live },
+        }
+      );
+
+      await db.waitReady;
+
+      let updatedResults;
+      const eventTarget = new EventTarget();
+      const { initialResults, unsubscribe } = await db.live.query(
+        "SELECT * FROM test ORDER BY name;",
+        [],
+        (result) => {
+          updatedResults = result;
+          eventTarget.dispatchEvent(new Event("updated"));
+        }
+      );
+      await new Promise((resolve) => {
+        eventTarget.addEventListener("updated", resolve);
+      });
+      return { initialResults, updatedResults };
+    });
+
+    const res1 = await evaluate(async () => {
+      const { live } = await import("../../dist/live/index.js");
+      const { PGliteWorker } = await import("../../dist/worker/index.js");
+
+      let db;
+      db = new PGliteWorker(
+        new Worker("/tests/targets/worker.js", {
+          type: "module",
+        }),
+        {
+          dataDir: window.dbFilename,
+          extensions: { live },
+        }
+      );
+
+      await db.waitReady;
+
+      let updatedResults;
+      const eventTarget = new EventTarget();
+      const { initialResults, unsubscribe } = await db.live.query(
+        "SELECT * FROM test ORDER BY name;",
+        [],
+        (result) => {
+          updatedResults = result;
+          eventTarget.dispatchEvent(new Event("updated"));
+        }
+      );
+      await new Promise((resolve) => setTimeout(resolve, 500));
+      await db.query("INSERT INTO test (id, name) VALUES (3, 'test3');");
+      await new Promise((resolve) => {
+        eventTarget.addEventListener("updated", resolve);
+      });
+      return { initialResults, updatedResults };
+    });
+
+    const res2 = await res2Prom;
+
+    t.deepEqual(res1.initialResults.rows, [
+      {
+        id: 1,
+        name: "test",
+      },
+      {
+        id: 2,
+        name: "test2",
+      },
+    ]);
+
+    for (const res of [res1, res2]) {
+      t.deepEqual(res.updatedResults.rows, [
+        {
+          id: 1,
+          name: "test",
+        },
+        {
+          id: 2,
+          name: "test2",
+        },
+        {
+          id: 3,
+          name: "test3",
+        },
+      ]);
+    }
+  });
+
+  test.serial(`targets ${target} worker live incremental query`, async (t) => {
+    const page2 = await context.newPage();
+    await page2.goto(`http://localhost:${wsPort}/tests/blank.html`);
+    page2.evaluate(`window.dbFilename = "${dbFilename}";`);
+    page.on("console", (msg) => {
+      console.log(msg);
+    });
+
+    const res2Prom = page2.evaluate(async () => {
+      const { live } = await import("../../dist/live/index.js");
+      const { PGliteWorker } = await import("../../dist/worker/index.js");
+
+      let db;
+      db = new PGliteWorker(
+        new Worker("/tests/targets/worker.js", {
+          type: "module",
+        }),
+        {
+          dataDir: window.dbFilename,
+          extensions: { live },
+        }
+      );
+
+      await db.waitReady;
+
+      let updatedResults;
+      const eventTarget = new EventTarget();
+      const { initialResults, unsubscribe } = await db.live.incrementalQuery(
+        "SELECT * FROM test ORDER BY name;",
+        [],
+        "id",
+        (result) => {
+          updatedResults = result;
+          eventTarget.dispatchEvent(new Event("updated"));
+        }
+      );
+      await new Promise((resolve) => {
+        eventTarget.addEventListener("updated", resolve);
+      });
+      return { initialResults, updatedResults };
+    });
+
+    const res1 = await evaluate(async () => {
+      const { live } = await import("../../dist/live/index.js");
+      const { PGliteWorker } = await import("../../dist/worker/index.js");
+
+      let db;
+      db = new PGliteWorker(
+        new Worker("/tests/targets/worker.js", {
+          type: "module",
+        }),
+        {
+          dataDir: window.dbFilename,
+          extensions: { live },
+        }
+      );
+
+      await db.waitReady;
+
+      let updatedResults;
+      const eventTarget = new EventTarget();
+      const { initialResults, unsubscribe } = await db.live.incrementalQuery(
+        "SELECT * FROM test ORDER BY name;",
+        [],
+        "id",
+        (result) => {
+          updatedResults = result;
+          eventTarget.dispatchEvent(new Event("updated"));
+        }
+      );
+      await new Promise((resolve) => setTimeout(resolve, 500));
+      const ret = await db.query(
+        "INSERT INTO test (id, name) VALUES (4, 'test4');"
+      );
+      await new Promise((resolve) => {
+        eventTarget.addEventListener("updated", resolve);
+      });
+      return { initialResults, updatedResults };
+    });
+
+    const res2 = await res2Prom;
+
+    t.deepEqual(res1.initialResults.rows, [
+      {
+        __after__: null,
+        id: 1,
+        name: "test",
+      },
+      {
+        __after__: 1,
+        id: 2,
+        name: "test2",
+      },
+      {
+        __after__: 2,
+        id: 3,
+        name: "test3",
+      },
+    ]);
+
+    for (const res of [res1, res2]) {
+      t.deepEqual(res.updatedResults.rows, [
+        {
+          __after__: null,
+          id: 1,
+          name: "test",
+        },
+        {
+          __after__: 1,
+          id: 2,
+          name: "test2",
+        },
+        {
+          __after__: 2,
+          id: 3,
+          name: "test3",
+        },
+        {
+          __after__: 3,
+          id: 4,
+          name: "test4",
+        },
+      ]);
+    }
+  });
 }
diff --git a/packages/pglite/tests/targets/chromium-opfs-ahp.test.js b/packages/pglite/tests/targets/chromium-opfs-ahp.test.js
new file mode 100644
index 00000000..901b2ef2
--- /dev/null
+++ b/packages/pglite/tests/targets/chromium-opfs-ahp.test.js
@@ -0,0 +1,3 @@
+import { tests } from "./base.js";
+
+tests("chromium", "opfs-ahp://base", "chromium.opfs-ahp");
diff --git a/packages/pglite/tests/targets/firefox-opfs-ahp.test.js b/packages/pglite/tests/targets/firefox-opfs-ahp.test.js
new file mode 100644
index 00000000..0bb9cddc
--- /dev/null
+++ b/packages/pglite/tests/targets/firefox-opfs-ahp.test.js
@@ -0,0 +1,3 @@
+import { tests } from "./base.js";
+
+tests("firefox", "opfs-ahp://base", "firefox.opfs-ahp");
diff --git a/packages/pglite/tests/targets/webkit-opfs-ahp.test.js b/packages/pglite/tests/targets/webkit-opfs-ahp.test.js
new file mode 100644
index 00000000..5f884dda
--- /dev/null
+++ b/packages/pglite/tests/targets/webkit-opfs-ahp.test.js
@@ -0,0 +1,5 @@
+import { tests } from "./base.js";
+
+// There is an issue with webkit opening more than 252 access handles, this
+// prevents the opfs-ahp VFS working on webkit. :-(
+// tests("webkit", "opfs-ahp://base", "webkit.opfs-ahp");
diff --git a/packages/pglite/tests/targets/worker.js b/packages/pglite/tests/targets/worker.js
new file mode 100644
index 00000000..a3a0b4a5
--- /dev/null
+++ b/packages/pglite/tests/targets/worker.js
@@ -0,0 +1,10 @@
+import { PGlite } from "../../../dist/index.js";
+import { worker } from "../../../dist/worker/index.js";
+
+worker({
+  async init(options) {
+    return new PGlite({
+      dataDir: options.dataDir,
+    });
+  },
+});
diff --git a/packages/pglite/tsup.config.ts b/packages/pglite/tsup.config.ts
index 3b3c1b16..736c6391 100644
--- a/packages/pglite/tsup.config.ts
+++ b/packages/pglite/tsup.config.ts
@@ -1,6 +1,7 @@
 import { defineConfig } from "tsup";
 import path from "path";
 import { fileURLToPath } from "url";
+import fs from "fs";
 
 const thisFile = fileURLToPath(new URL(import.meta.url));
 const root = path.dirname(thisFile);
@@ -19,10 +20,19 @@ const entryPoints = [
   "src/index.ts",
   'src/live/index.ts',
   "src/worker/index.ts",
-  "src/worker/process.ts",
   "src/vector/index.ts",
+  "src/fs/opfs-ahp/index.ts",
+  "src/fs/nodefs.ts",
 ];
 
+const contribDir = path.join(root, "src", "contrib");
+const contribFiles = await fs.promises.readdir(contribDir);
+for (const file of contribFiles) {
+  if (file.endsWith(".ts")) {
+    entryPoints.push(`src/contrib/${file}`);
+  }
+}
+
 export default defineConfig({
   entry: entryPoints,
   sourcemap: true,
@@ -32,7 +42,7 @@ export default defineConfig({
   },
   clean: true,
   format: ["esm"],
-  external: ["./postgres.js"],
+  external: ["../release/postgres.js"],
   esbuildOptions(options, context) {
     options.inject = [
       "src/polyfills/buffer.ts",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44955bb4..efb0269c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,8 +16,8 @@ importers:
   packages/benchmark:
     dependencies:
       wa-sqlite:
-        specifier: github:rhashimoto/wa-sqlite
-        version: github.com/rhashimoto/wa-sqlite/f1ab3d5d795fb4156e0b8e20ce2f58c05dfee74a
+        specifier: github:rhashimoto/wa-sqlite#v0.9.14
+        version: github.com/rhashimoto/wa-sqlite/6dbe4f044502a7a285e815896f56304a808fe25b
     devDependencies:
       '@types/better-sqlite3':
         specifier: ^7.6.9
@@ -58,9 +58,6 @@ importers:
       bun:
         specifier: ^1.1.18
         version: 1.1.18
-      comlink:
-        specifier: ^4.4.1
-        version: 4.4.1
       concurrently:
         specifier: ^8.2.2
         version: 8.2.2
@@ -76,12 +73,15 @@ importers:
       prettier:
         specifier: 3.2.5
         version: 3.2.5
+      serve:
+        specifier: ^14.2.3
+        version: 14.2.3
       tinytar:
         specifier: ^0.1.0
         version: 0.1.0
       tsup:
-        specifier: ^8.0.2
-        version: 8.0.2(typescript@5.3.3)
+        specifier: ^8.1.0
+        version: 8.2.3(tsx@4.7.1)(typescript@5.3.3)
       tsx:
         specifier: ^4.7.1
         version: 4.7.1
@@ -579,6 +579,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/aix-ppc64@0.23.0:
+    resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/android-arm64@0.19.12:
     resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
     engines: {node: '>=12'}
@@ -597,6 +606,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/android-arm64@0.23.0:
+    resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/android-arm@0.19.12:
     resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
     engines: {node: '>=12'}
@@ -615,6 +633,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/android-arm@0.23.0:
+    resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/android-x64@0.19.12:
     resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
     engines: {node: '>=12'}
@@ -633,6 +660,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/android-x64@0.23.0:
+    resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/darwin-arm64@0.19.12:
     resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
     engines: {node: '>=12'}
@@ -651,6 +687,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/darwin-arm64@0.23.0:
+    resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/darwin-x64@0.19.12:
     resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
     engines: {node: '>=12'}
@@ -669,6 +714,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/darwin-x64@0.23.0:
+    resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/freebsd-arm64@0.19.12:
     resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
     engines: {node: '>=12'}
@@ -687,6 +741,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/freebsd-arm64@0.23.0:
+    resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/freebsd-x64@0.19.12:
     resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
     engines: {node: '>=12'}
@@ -705,6 +768,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/freebsd-x64@0.23.0:
+    resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-arm64@0.19.12:
     resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
     engines: {node: '>=12'}
@@ -723,6 +795,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-arm64@0.23.0:
+    resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-arm@0.19.12:
     resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
     engines: {node: '>=12'}
@@ -741,6 +822,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-arm@0.23.0:
+    resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-ia32@0.19.12:
     resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
     engines: {node: '>=12'}
@@ -759,6 +849,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-ia32@0.23.0:
+    resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-loong64@0.19.12:
     resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
     engines: {node: '>=12'}
@@ -777,6 +876,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-loong64@0.23.0:
+    resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-mips64el@0.19.12:
     resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
     engines: {node: '>=12'}
@@ -795,6 +903,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-mips64el@0.23.0:
+    resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-ppc64@0.19.12:
     resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
     engines: {node: '>=12'}
@@ -813,6 +930,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-ppc64@0.23.0:
+    resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-riscv64@0.19.12:
     resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
     engines: {node: '>=12'}
@@ -831,6 +957,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-riscv64@0.23.0:
+    resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-s390x@0.19.12:
     resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
     engines: {node: '>=12'}
@@ -849,6 +984,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-s390x@0.23.0:
+    resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/linux-x64@0.19.12:
     resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
     engines: {node: '>=12'}
@@ -867,6 +1011,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/linux-x64@0.23.0:
+    resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/netbsd-x64@0.19.12:
     resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
     engines: {node: '>=12'}
@@ -885,6 +1038,24 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/netbsd-x64@0.23.0:
+    resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /@esbuild/openbsd-arm64@0.23.0:
+    resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/openbsd-x64@0.19.12:
     resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
     engines: {node: '>=12'}
@@ -903,6 +1074,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/openbsd-x64@0.23.0:
+    resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/sunos-x64@0.19.12:
     resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
     engines: {node: '>=12'}
@@ -921,6 +1101,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/sunos-x64@0.23.0:
+    resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/win32-arm64@0.19.12:
     resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
     engines: {node: '>=12'}
@@ -939,6 +1128,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/win32-arm64@0.23.0:
+    resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/win32-ia32@0.19.12:
     resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
     engines: {node: '>=12'}
@@ -957,6 +1155,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/win32-ia32@0.23.0:
+    resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@esbuild/win32-x64@0.19.12:
     resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
     engines: {node: '>=12'}
@@ -975,6 +1182,15 @@ packages:
     dev: true
     optional: true
 
+  /@esbuild/win32-x64@0.23.0:
+    resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0):
     resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1046,15 +1262,6 @@ packages:
       wrap-ansi-cjs: /wrap-ansi@7.0.0
     dev: true
 
-  /@jridgewell/gen-mapping@0.3.3:
-    resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
-    engines: {node: '>=6.0.0'}
-    dependencies:
-      '@jridgewell/set-array': 1.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
-      '@jridgewell/trace-mapping': 0.3.22
-    dev: true
-
   /@jridgewell/gen-mapping@0.3.5:
     resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
     engines: {node: '>=6.0.0'}
@@ -1069,11 +1276,6 @@ packages:
     engines: {node: '>=6.0.0'}
     dev: true
 
-  /@jridgewell/set-array@1.1.2:
-    resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
-    engines: {node: '>=6.0.0'}
-    dev: true
-
   /@jridgewell/set-array@1.2.1:
     resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
     engines: {node: '>=6.0.0'}
@@ -1090,13 +1292,6 @@ packages:
     resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
     dev: true
 
-  /@jridgewell/trace-mapping@0.3.22:
-    resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==}
-    dependencies:
-      '@jridgewell/resolve-uri': 3.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
-    dev: true
-
   /@jridgewell/trace-mapping@0.3.25:
     resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
     dependencies:
@@ -1296,80 +1491,80 @@ packages:
       picomatch: 2.3.1
     dev: true
 
-  /@rollup/rollup-android-arm-eabi@4.12.0:
-    resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==}
+  /@rollup/rollup-android-arm-eabi@4.18.0:
+    resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==}
     cpu: [arm]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-android-arm-eabi@4.18.0:
-    resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==}
+  /@rollup/rollup-android-arm-eabi@4.19.1:
+    resolution: {integrity: sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==}
     cpu: [arm]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-android-arm64@4.12.0:
-    resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==}
+  /@rollup/rollup-android-arm64@4.18.0:
+    resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-android-arm64@4.18.0:
-    resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==}
+  /@rollup/rollup-android-arm64@4.19.1:
+    resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-arm64@4.12.0:
-    resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==}
+  /@rollup/rollup-darwin-arm64@4.18.0:
+    resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-arm64@4.18.0:
-    resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==}
+  /@rollup/rollup-darwin-arm64@4.19.1:
+    resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-x64@4.12.0:
-    resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==}
+  /@rollup/rollup-darwin-x64@4.18.0:
+    resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-x64@4.18.0:
-    resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==}
+  /@rollup/rollup-darwin-x64@4.19.1:
+    resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm-gnueabihf@4.12.0:
-    resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==}
+  /@rollup/rollup-linux-arm-gnueabihf@4.18.0:
+    resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm-gnueabihf@4.18.0:
-    resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==}
+  /@rollup/rollup-linux-arm-gnueabihf@4.19.1:
+    resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
@@ -1384,9 +1579,9 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-gnu@4.12.0:
-    resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==}
-    cpu: [arm64]
+  /@rollup/rollup-linux-arm-musleabihf@4.19.1:
+    resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==}
+    cpu: [arm]
     os: [linux]
     requiresBuild: true
     dev: true
@@ -1400,8 +1595,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-musl@4.12.0:
-    resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==}
+  /@rollup/rollup-linux-arm64-gnu@4.19.1:
+    resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
@@ -1416,6 +1611,14 @@ packages:
     dev: true
     optional: true
 
+  /@rollup/rollup-linux-arm64-musl@4.19.1:
+    resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@rollup/rollup-linux-powerpc64le-gnu@4.18.0:
     resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==}
     cpu: [ppc64]
@@ -1424,9 +1627,9 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-riscv64-gnu@4.12.0:
-    resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==}
-    cpu: [riscv64]
+  /@rollup/rollup-linux-powerpc64le-gnu@4.19.1:
+    resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==}
+    cpu: [ppc64]
     os: [linux]
     requiresBuild: true
     dev: true
@@ -1440,6 +1643,14 @@ packages:
     dev: true
     optional: true
 
+  /@rollup/rollup-linux-riscv64-gnu@4.19.1:
+    resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==}
+    cpu: [riscv64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@rollup/rollup-linux-s390x-gnu@4.18.0:
     resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==}
     cpu: [s390x]
@@ -1448,9 +1659,9 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-x64-gnu@4.12.0:
-    resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==}
-    cpu: [x64]
+  /@rollup/rollup-linux-s390x-gnu@4.19.1:
+    resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==}
+    cpu: [s390x]
     os: [linux]
     requiresBuild: true
     dev: true
@@ -1464,8 +1675,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-x64-musl@4.12.0:
-    resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==}
+  /@rollup/rollup-linux-x64-gnu@4.19.1:
+    resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
@@ -1480,10 +1691,10 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-arm64-msvc@4.12.0:
-    resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==}
-    cpu: [arm64]
-    os: [win32]
+  /@rollup/rollup-linux-x64-musl@4.19.1:
+    resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==}
+    cpu: [x64]
+    os: [linux]
     requiresBuild: true
     dev: true
     optional: true
@@ -1496,9 +1707,9 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-ia32-msvc@4.12.0:
-    resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==}
-    cpu: [ia32]
+  /@rollup/rollup-win32-arm64-msvc@4.19.1:
+    resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==}
+    cpu: [arm64]
     os: [win32]
     requiresBuild: true
     dev: true
@@ -1512,9 +1723,9 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-x64-msvc@4.12.0:
-    resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==}
-    cpu: [x64]
+  /@rollup/rollup-win32-ia32-msvc@4.19.1:
+    resolution: {integrity: sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==}
+    cpu: [ia32]
     os: [win32]
     requiresBuild: true
     dev: true
@@ -1528,6 +1739,14 @@ packages:
     dev: true
     optional: true
 
+  /@rollup/rollup-win32-x64-msvc@4.19.1:
+    resolution: {integrity: sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /@rushstack/node-core-library@4.0.2:
     resolution: {integrity: sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==}
     peerDependencies:
@@ -1954,10 +2173,22 @@ packages:
     resolution: {integrity: sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==}
     dev: true
 
+  /@zeit/schemas@2.36.0:
+    resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==}
+    dev: true
+
   /abbrev@1.1.1:
     resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
     dev: true
 
+  /accepts@1.3.8:
+    resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+    engines: {node: '>= 0.6'}
+    dependencies:
+      mime-types: 2.1.35
+      negotiator: 0.6.3
+    dev: true
+
   /acorn-import-attributes@1.9.2(acorn@8.11.3):
     resolution: {integrity: sha512-O+nfJwNolEA771IYJaiLWK1UAwjNsQmZbTRqqwBYxCgVQTmpFEMvBw6LOIQV0Me339L5UMVYFyRohGnGlQDdIQ==}
     peerDependencies:
@@ -2003,6 +2234,21 @@ packages:
       uri-js: 4.4.1
     dev: true
 
+  /ajv@8.12.0:
+    resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+    dependencies:
+      fast-deep-equal: 3.1.3
+      json-schema-traverse: 1.0.0
+      require-from-string: 2.0.2
+      uri-js: 4.4.1
+    dev: true
+
+  /ansi-align@3.0.1:
+    resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+    dependencies:
+      string-width: 4.2.3
+    dev: true
+
   /ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
@@ -2048,6 +2294,10 @@ packages:
     resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
     dev: true
 
+  /arch@2.2.0:
+    resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
+    dev: true
+
   /are-we-there-yet@2.0.0:
     resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
     engines: {node: '>=10'}
@@ -2056,6 +2306,10 @@ packages:
       readable-stream: 3.6.2
     dev: true
 
+  /arg@5.0.2:
+    resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+    dev: true
+
   /argparse@1.0.10:
     resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
     dependencies:
@@ -2219,6 +2473,20 @@ packages:
     resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==}
     dev: true
 
+  /boxen@7.0.0:
+    resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==}
+    engines: {node: '>=14.16'}
+    dependencies:
+      ansi-align: 3.0.1
+      camelcase: 7.0.1
+      chalk: 5.3.0
+      cli-boxes: 3.0.0
+      string-width: 5.1.2
+      type-fest: 2.19.0
+      widest-line: 4.0.1
+      wrap-ansi: 8.1.0
+    dev: true
+
   /brace-expansion@1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
     dependencies:
@@ -2290,16 +2558,21 @@ packages:
       '@oven/bun-windows-x64-baseline': 1.1.18
     dev: true
 
-  /bundle-require@4.0.2(esbuild@0.19.12):
-    resolution: {integrity: sha512-jwzPOChofl67PSTW2SGubV9HBQAhhR2i6nskiOThauo9dzwDUgOWQScFVaJkjEfYX+UXiD+LEx8EblQMc2wIag==}
+  /bundle-require@5.0.0(esbuild@0.23.0):
+    resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
     peerDependencies:
-      esbuild: '>=0.17'
+      esbuild: '>=0.18'
     dependencies:
-      esbuild: 0.19.12
+      esbuild: 0.23.0
       load-tsconfig: 0.2.5
     dev: true
 
+  /bytes@3.0.0:
+    resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
+    engines: {node: '>= 0.8'}
+    dev: true
+
   /cac@6.7.14:
     resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
     engines: {node: '>=8'}
@@ -2326,6 +2599,11 @@ packages:
     engines: {node: '>=12.20'}
     dev: true
 
+  /camelcase@7.0.1:
+    resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
+    engines: {node: '>=14.16'}
+    dev: true
+
   /caniuse-lite@1.0.30001636:
     resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==}
     dev: true
@@ -2337,6 +2615,13 @@ packages:
       nofilter: 3.1.0
     dev: true
 
+  /chalk-template@0.4.0:
+    resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
+    engines: {node: '>=12'}
+    dependencies:
+      chalk: 4.1.2
+    dev: true
+
   /chalk@2.4.2:
     resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
     engines: {node: '>=4'}
@@ -2354,6 +2639,11 @@ packages:
       supports-color: 7.2.0
     dev: true
 
+  /chalk@5.0.1:
+    resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
+    engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+    dev: true
+
   /chalk@5.3.0:
     resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
     engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
@@ -2396,6 +2686,11 @@ packages:
     resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==}
     dev: true
 
+  /cli-boxes@3.0.0:
+    resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
+    engines: {node: '>=10'}
+    dev: true
+
   /cli-truncate@4.0.0:
     resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
     engines: {node: '>=18'}
@@ -2404,6 +2699,15 @@ packages:
       string-width: 7.1.0
     dev: true
 
+  /clipboardy@3.0.0:
+    resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      arch: 2.2.0
+      execa: 5.1.1
+      is-wsl: 2.2.0
+    dev: true
+
   /cliui@8.0.1:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
@@ -2467,10 +2771,6 @@ packages:
       delayed-stream: 1.0.0
     dev: true
 
-  /comlink@4.4.1:
-    resolution: {integrity: sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==}
-    dev: true
-
   /commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
     dev: true
@@ -2491,6 +2791,28 @@ packages:
     resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==}
     dev: true
 
+  /compressible@2.0.18:
+    resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
+    engines: {node: '>= 0.6'}
+    dependencies:
+      mime-db: 1.52.0
+    dev: true
+
+  /compression@1.7.4:
+    resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
+    engines: {node: '>= 0.8.0'}
+    dependencies:
+      accepts: 1.3.8
+      bytes: 3.0.0
+      compressible: 2.0.18
+      debug: 2.6.9
+      on-headers: 1.0.2
+      safe-buffer: 5.1.2
+      vary: 1.1.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /computeds@0.0.1:
     resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
     dev: true
@@ -2529,10 +2851,20 @@ packages:
       yargs: 17.7.2
     dev: true
 
+  /consola@3.2.3:
+    resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==}
+    engines: {node: ^14.18.0 || >=16.10.0}
+    dev: true
+
   /console-control-strings@1.1.0:
     resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
     dev: true
 
+  /content-disposition@0.5.2:
+    resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
+    engines: {node: '>= 0.6'}
+    dev: true
+
   /convert-source-map@2.0.0:
     resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
     dev: true
@@ -2589,6 +2921,17 @@ packages:
     resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
     dev: true
 
+  /debug@2.6.9:
+    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+    dependencies:
+      ms: 2.0.0
+    dev: true
+
   /debug@3.2.7:
     resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
     peerDependencies:
@@ -2612,6 +2955,18 @@ packages:
       ms: 2.1.2
     dev: true
 
+  /debug@4.3.6:
+    resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+    dependencies:
+      ms: 2.1.2
+    dev: true
+
   /decompress-response@6.0.0:
     resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
     engines: {node: '>=10'}
@@ -2793,6 +3148,38 @@ packages:
       '@esbuild/win32-x64': 0.21.5
     dev: true
 
+  /esbuild@0.23.0:
+    resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==}
+    engines: {node: '>=18'}
+    hasBin: true
+    requiresBuild: true
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.23.0
+      '@esbuild/android-arm': 0.23.0
+      '@esbuild/android-arm64': 0.23.0
+      '@esbuild/android-x64': 0.23.0
+      '@esbuild/darwin-arm64': 0.23.0
+      '@esbuild/darwin-x64': 0.23.0
+      '@esbuild/freebsd-arm64': 0.23.0
+      '@esbuild/freebsd-x64': 0.23.0
+      '@esbuild/linux-arm': 0.23.0
+      '@esbuild/linux-arm64': 0.23.0
+      '@esbuild/linux-ia32': 0.23.0
+      '@esbuild/linux-loong64': 0.23.0
+      '@esbuild/linux-mips64el': 0.23.0
+      '@esbuild/linux-ppc64': 0.23.0
+      '@esbuild/linux-riscv64': 0.23.0
+      '@esbuild/linux-s390x': 0.23.0
+      '@esbuild/linux-x64': 0.23.0
+      '@esbuild/netbsd-x64': 0.23.0
+      '@esbuild/openbsd-arm64': 0.23.0
+      '@esbuild/openbsd-x64': 0.23.0
+      '@esbuild/sunos-x64': 0.23.0
+      '@esbuild/win32-arm64': 0.23.0
+      '@esbuild/win32-ia32': 0.23.0
+      '@esbuild/win32-x64': 0.23.0
+    dev: true
+
   /escalade@3.1.2:
     resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
     engines: {node: '>=6'}
@@ -2989,6 +3376,12 @@ packages:
     resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
     dev: true
 
+  /fast-url-parser@1.1.3:
+    resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==}
+    dependencies:
+      punycode: 1.4.1
+    dev: true
+
   /fastq@1.17.1:
     resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
     dependencies:
@@ -3194,7 +3587,7 @@ packages:
     dependencies:
       foreground-child: 3.1.1
       jackspeak: 2.3.6
-      minimatch: 9.0.3
+      minimatch: 9.0.4
       minipass: 7.0.4
       path-scurry: 1.10.1
     dev: true
@@ -3435,6 +3828,12 @@ packages:
       hasown: 2.0.1
     dev: true
 
+  /is-docker@2.2.1:
+    resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
+    engines: {node: '>=8'}
+    hasBin: true
+    dev: true
+
   /is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
@@ -3472,6 +3871,11 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /is-port-reachable@4.0.0:
+    resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /is-promise@4.0.0:
     resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
     dev: true
@@ -3486,6 +3890,13 @@ packages:
     engines: {node: '>=18'}
     dev: true
 
+  /is-wsl@2.2.0:
+    resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
+    engines: {node: '>=8'}
+    dependencies:
+      is-docker: 2.2.1
+    dev: true
+
   /isexe@2.0.0:
     resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
     dev: true
@@ -3545,6 +3956,10 @@ packages:
     resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
     dev: true
 
+  /json-schema-traverse@1.0.0:
+    resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+    dev: true
+
   /json-stable-stringify-without-jsonify@1.0.1:
     resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
     dev: true
@@ -3701,11 +4116,23 @@ packages:
       picomatch: 2.3.1
     dev: true
 
+  /mime-db@1.33.0:
+    resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
+    engines: {node: '>= 0.6'}
+    dev: true
+
   /mime-db@1.52.0:
     resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
     engines: {node: '>= 0.6'}
     dev: true
 
+  /mime-types@2.1.18:
+    resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
+    engines: {node: '>= 0.6'}
+    dependencies:
+      mime-db: 1.33.0
+    dev: true
+
   /mime-types@2.1.35:
     resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
     engines: {node: '>= 0.6'}
@@ -3806,6 +4233,10 @@ packages:
     hasBin: true
     dev: true
 
+  /ms@2.0.0:
+    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+    dev: true
+
   /ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
     dev: true
@@ -3840,6 +4271,11 @@ packages:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
 
+  /negotiator@0.6.3:
+    resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+    engines: {node: '>= 0.6'}
+    dev: true
+
   /node-abi@3.56.0:
     resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==}
     engines: {node: '>=10'}
@@ -3911,6 +4347,11 @@ packages:
     resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
     dev: true
 
+  /on-headers@1.0.2:
+    resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
+    engines: {node: '>= 0.8'}
+    dev: true
+
   /once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
     dependencies:
@@ -3998,6 +4439,10 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /path-is-inside@1.0.2:
+    resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
+    dev: true
+
   /path-key@3.1.1:
     resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
     engines: {node: '>=8'}
@@ -4015,6 +4460,10 @@ packages:
       minipass: 7.0.4
     dev: true
 
+  /path-to-regexp@2.2.1:
+    resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==}
+    dev: true
+
   /path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
@@ -4147,20 +4596,26 @@ packages:
       - supports-color
     dev: true
 
-  /postcss-load-config@4.0.2:
-    resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
-    engines: {node: '>= 14'}
+  /postcss-load-config@6.0.1(tsx@4.7.1):
+    resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+    engines: {node: '>= 18'}
     peerDependencies:
+      jiti: '>=1.21.0'
       postcss: '>=8.0.9'
-      ts-node: '>=9.0.0'
+      tsx: ^4.8.1
+      yaml: ^2.4.2
     peerDependenciesMeta:
+      jiti:
+        optional: true
       postcss:
         optional: true
-      ts-node:
+      tsx:
+        optional: true
+      yaml:
         optional: true
     dependencies:
       lilconfig: 3.1.1
-      yaml: 2.3.4
+      tsx: 4.7.1
     dev: true
 
   /postcss@8.4.38:
@@ -4246,6 +4701,10 @@ packages:
       once: 1.4.0
     dev: true
 
+  /punycode@1.4.1:
+    resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
+    dev: true
+
   /punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -4262,6 +4721,11 @@ packages:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
     dev: true
 
+  /range-parser@1.2.0:
+    resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
+    engines: {node: '>= 0.6'}
+    dev: true
+
   /rc@1.2.8:
     resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
     hasBin: true
@@ -4313,11 +4777,30 @@ packages:
   /regenerator-runtime@0.14.1:
     resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
 
+  /registry-auth-token@3.3.2:
+    resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
+    dependencies:
+      rc: 1.2.8
+      safe-buffer: 5.2.1
+    dev: true
+
+  /registry-url@3.1.0:
+    resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      rc: 1.2.8
+    dev: true
+
   /require-directory@2.1.1:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /require-from-string@2.0.2:
+    resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
   /requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
     dev: true
@@ -4371,29 +4854,6 @@ packages:
       glob: 7.2.3
     dev: true
 
-  /rollup@4.12.0:
-    resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==}
-    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
-    hasBin: true
-    dependencies:
-      '@types/estree': 1.0.5
-    optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.12.0
-      '@rollup/rollup-android-arm64': 4.12.0
-      '@rollup/rollup-darwin-arm64': 4.12.0
-      '@rollup/rollup-darwin-x64': 4.12.0
-      '@rollup/rollup-linux-arm-gnueabihf': 4.12.0
-      '@rollup/rollup-linux-arm64-gnu': 4.12.0
-      '@rollup/rollup-linux-arm64-musl': 4.12.0
-      '@rollup/rollup-linux-riscv64-gnu': 4.12.0
-      '@rollup/rollup-linux-x64-gnu': 4.12.0
-      '@rollup/rollup-linux-x64-musl': 4.12.0
-      '@rollup/rollup-win32-arm64-msvc': 4.12.0
-      '@rollup/rollup-win32-ia32-msvc': 4.12.0
-      '@rollup/rollup-win32-x64-msvc': 4.12.0
-      fsevents: 2.3.3
-    dev: true
-
   /rollup@4.18.0:
     resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -4420,6 +4880,32 @@ packages:
       fsevents: 2.3.3
     dev: true
 
+  /rollup@4.19.1:
+    resolution: {integrity: sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+    dependencies:
+      '@types/estree': 1.0.5
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.19.1
+      '@rollup/rollup-android-arm64': 4.19.1
+      '@rollup/rollup-darwin-arm64': 4.19.1
+      '@rollup/rollup-darwin-x64': 4.19.1
+      '@rollup/rollup-linux-arm-gnueabihf': 4.19.1
+      '@rollup/rollup-linux-arm-musleabihf': 4.19.1
+      '@rollup/rollup-linux-arm64-gnu': 4.19.1
+      '@rollup/rollup-linux-arm64-musl': 4.19.1
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.19.1
+      '@rollup/rollup-linux-riscv64-gnu': 4.19.1
+      '@rollup/rollup-linux-s390x-gnu': 4.19.1
+      '@rollup/rollup-linux-x64-gnu': 4.19.1
+      '@rollup/rollup-linux-x64-musl': 4.19.1
+      '@rollup/rollup-win32-arm64-msvc': 4.19.1
+      '@rollup/rollup-win32-ia32-msvc': 4.19.1
+      '@rollup/rollup-win32-x64-msvc': 4.19.1
+      fsevents: 2.3.3
+    dev: true
+
   /run-parallel@1.2.0:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
     dependencies:
@@ -4482,6 +4968,39 @@ packages:
       type-fest: 0.13.1
     dev: true
 
+  /serve-handler@6.1.5:
+    resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==}
+    dependencies:
+      bytes: 3.0.0
+      content-disposition: 0.5.2
+      fast-url-parser: 1.1.3
+      mime-types: 2.1.18
+      minimatch: 3.1.2
+      path-is-inside: 1.0.2
+      path-to-regexp: 2.2.1
+      range-parser: 1.2.0
+    dev: true
+
+  /serve@14.2.3:
+    resolution: {integrity: sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==}
+    engines: {node: '>= 14'}
+    hasBin: true
+    dependencies:
+      '@zeit/schemas': 2.36.0
+      ajv: 8.12.0
+      arg: 5.0.2
+      boxen: 7.0.0
+      chalk: 5.0.1
+      chalk-template: 0.4.0
+      clipboardy: 3.0.0
+      compression: 1.7.4
+      is-port-reachable: 4.0.0
+      serve-handler: 6.1.5
+      update-check: 1.5.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /set-blocking@2.0.0:
     resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
     dev: true
@@ -4683,7 +5202,7 @@ packages:
     engines: {node: '>=16 || 14 >=14.17'}
     hasBin: true
     dependencies:
-      '@jridgewell/gen-mapping': 0.3.3
+      '@jridgewell/gen-mapping': 0.3.5
       commander: 4.1.1
       glob: 10.3.10
       lines-and-columns: 1.2.4
@@ -4847,8 +5366,8 @@ packages:
     resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
     dev: true
 
-  /tsup@8.0.2(typescript@5.3.3):
-    resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==}
+  /tsup@8.2.3(tsx@4.7.1)(typescript@5.3.3):
+    resolution: {integrity: sha512-6YNT44oUfXRbZuSMNmN36GzwPPIlD2wBccY7looM2fkTcxkf2NEmwr3OZuDZoySklnrIG4hoEtzy8yUXYOqNcg==}
     engines: {node: '>=18'}
     hasBin: true
     peerDependencies:
@@ -4866,24 +5385,28 @@ packages:
       typescript:
         optional: true
     dependencies:
-      bundle-require: 4.0.2(esbuild@0.19.12)
+      bundle-require: 5.0.0(esbuild@0.23.0)
       cac: 6.7.14
       chokidar: 3.6.0
-      debug: 4.3.4
-      esbuild: 0.19.12
+      consola: 3.2.3
+      debug: 4.3.6
+      esbuild: 0.23.0
       execa: 5.1.1
       globby: 11.1.0
       joycon: 3.1.1
-      postcss-load-config: 4.0.2
+      picocolors: 1.0.1
+      postcss-load-config: 6.0.1(tsx@4.7.1)
       resolve-from: 5.0.0
-      rollup: 4.12.0
+      rollup: 4.19.1
       source-map: 0.8.0-beta.0
       sucrase: 3.35.0
       tree-kill: 1.2.2
       typescript: 5.3.3
     transitivePeerDependencies:
+      - jiti
       - supports-color
-      - ts-node
+      - tsx
+      - yaml
     dev: true
 
   /tsx@4.7.1:
@@ -4920,6 +5443,11 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
+  /type-fest@2.19.0:
+    resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
+    engines: {node: '>=12.20'}
+    dev: true
+
   /typescript@5.3.3:
     resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
     engines: {node: '>=14.17'}
@@ -4964,6 +5492,13 @@ packages:
       picocolors: 1.0.1
     dev: true
 
+  /update-check@1.5.4:
+    resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
+    dependencies:
+      registry-auth-token: 3.3.2
+      registry-url: 3.1.0
+    dev: true
+
   /uri-js@4.4.1:
     resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
     dependencies:
@@ -4983,6 +5518,11 @@ packages:
     engines: {node: '>= 0.10'}
     dev: true
 
+  /vary@1.1.2:
+    resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+    engines: {node: '>= 0.8'}
+    dev: true
+
   /vite-plugin-dts@3.9.1(typescript@5.3.3)(vite@5.3.1):
     resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==}
     engines: {node: ^14.18.0 || >=16.0.0}
@@ -5125,6 +5665,13 @@ packages:
       string-width: 4.2.3
     dev: true
 
+  /widest-line@4.0.1:
+    resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
+    engines: {node: '>=12'}
+    dependencies:
+      string-width: 5.1.2
+    dev: true
+
   /word-wrap@1.2.5:
     resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
     engines: {node: '>=0.10.0'}
@@ -5178,11 +5725,6 @@ packages:
     resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
     dev: true
 
-  /yaml@2.3.4:
-    resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
-    engines: {node: '>= 14'}
-    dev: true
-
   /yargs-parser@21.1.1:
     resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
     engines: {node: '>=12'}
@@ -5218,8 +5760,8 @@ packages:
       commander: 9.5.0
     dev: true
 
-  github.com/rhashimoto/wa-sqlite/f1ab3d5d795fb4156e0b8e20ce2f58c05dfee74a:
-    resolution: {tarball: https://codeload.github.com/rhashimoto/wa-sqlite/tar.gz/f1ab3d5d795fb4156e0b8e20ce2f58c05dfee74a}
+  github.com/rhashimoto/wa-sqlite/6dbe4f044502a7a285e815896f56304a808fe25b:
+    resolution: {tarball: https://codeload.github.com/rhashimoto/wa-sqlite/tar.gz/6dbe4f044502a7a285e815896f56304a808fe25b}
     name: wa-sqlite
-    version: 1.0.0
+    version: 0.9.14
     dev: false