Server

Advanced

Explicit imports, the provider behind usePGlite() and how the instance lives.

Explicit imports: #pglite/server

Everything the server side auto-imports is also exported by #pglite/server, for code that prefers explicit imports or a module building on this one:

server/utils/db.ts
import { pglite, usePGlite } from '#pglite/server'
import type { PGliteConfig, PGliteInstanceFor, PGliteServerAction } from '#pglite/server'

The client side has the same name for usePGlite(). Auto-imports resolve to the right one per context; #pglite/server and #pglite/client pick one explicitly.

The provider

usePGlite() is a shortcut for pglite.use(). The pglite provider holds the single instance of the process, since PGlite allows one instance per data directory:

import { pglite } from '#pglite/server'

const pg = await pglite.use() // created on first call; concurrent callers share the creation
pglite.instance // the instance if created and still open, otherwise undefined
await pglite.close() // runs `dispose`, then closes; the next use() creates a new one
  • A failed creation is not cached: the next use() tries again.
  • A closed instance is replaced on the next use().
  • A use() issued while close() runs waits for the close and gets a fresh instance.

The module calls pglite.close() when Nitro closes. Closing it yourself is useful in tests, or to release the data directory for another process.

init and usePGlite()

init runs inside the creation of the instance. Calling usePGlite() from there would wait for itself, so it fails right away:

server/pglite.config.ts
export default definePGliteServerConfig({
  init: async (pg) => {
    await seed(pg) // ✅ use the instance `init` receives
  },
})

async function seed(pg: { exec(sql: string): Promise<unknown> }) {
  await pg.exec('INSERT INTO settings DEFAULT VALUES')
  // ❌ `await usePGlite()` here throws: "The instance is still initialising…"
}

The check follows the asynchronous extent of init through AsyncLocalStorage. On a runtime without node:async_hooks the call is not detected and would hang instead, so keep init self-contained.

Typing a config written elsewhere

PGliteInstanceFor<typeof config> is the instance type a config produces, which helps when the instance is passed around:

server/utils/vector.ts
import type { PGliteInstanceFor } from '#pglite/server'
import type config from '../pglite.config'

export async function similar(pg: PGliteInstanceFor<typeof config>, embedding: number[]) {
  return pg.query('SELECT id FROM items ORDER BY embedding <-> $1 LIMIT 5', [
    JSON.stringify(embedding),
  ])
}

When the socket is running

With the development socket enabled, the socket's instance is created in the Nuxt process, from the same config, and holds the configured data directory. usePGlite() in your routes then refuses that directory, since PGlite allows one instance per directory. Connect through the socket URL instead (useRuntimeConfig().pglite.url), or point the server instance elsewhere with NUXT_PGLITE_DATA_DIR: the config file and nuxt.config.ts options apply to both instances, the runtime override only to the one in Nitro.