Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/curly-taxis-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite-socket': patch
---

allow extensions to be loaded via '-e/--extensions <list>' cmd line parameter'
1 change: 1 addition & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
working-directory: ./packages/pglite
needs: [build-all]
steps:

- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
Expand Down
1 change: 1 addition & 0 deletions packages/pglite-socket/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ pglite-server --help
- `-h, --host=HOST` - Host to bind to (default: 127.0.0.1)
- `-u, --path=UNIX` - Unix socket to bind to (takes precedence over host:port)
- `-v, --debug=LEVEL` - Debug level 0-5 (default: 0)
- `-e, --extensions=LIST` - Comma-separated list of extensions to load (e.g., vector,pgcrypto)
- `-r, --run=COMMAND` - Command to run after server starts
- `--include-database-url` - Include DATABASE_URL in subprocess environment
- `--shutdown-timeout=MS` - Timeout for graceful subprocess shutdown in ms (default: 5000)
Expand Down
70 changes: 69 additions & 1 deletion packages/pglite-socket/src/scripts/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import { PGlite, DebugLevel } from '@electric-sql/pglite'
import type { Extension, Extensions } from '@electric-sql/pglite'
import { PGLiteSocketServer } from '../index'
import { parseArgs } from 'node:util'
import { spawn, ChildProcess } from 'node:child_process'
Expand Down Expand Up @@ -38,6 +39,12 @@ const args = parseArgs({
default: '0',
help: 'Debug level (0-5)',
},
extensions: {
type: 'string',
short: 'e',
default: undefined,
help: 'Comma-separated list of extensions to load (e.g., vector,pgcrypto)',
},
run: {
type: 'string',
short: 'r',
Expand Down Expand Up @@ -72,6 +79,7 @@ Options:
-h, --host=HOST Host to bind to (default: 127.0.0.1)
-u, --path=UNIX Unix socket to bind to (default: undefined). Takes precedence over host:port
-v, --debug=LEVEL Debug level 0-5 (default: 0)
-e, --extensions=LIST Comma-separated list of extensions to load (e.g., vector,pgcrypto)
-r, --run=COMMAND Command to run after server starts
--include-database-url Include DATABASE_URL in subprocess environment
--shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000)
Expand All @@ -83,6 +91,7 @@ interface ServerConfig {
host: string
path?: string
debugLevel: DebugLevel
extensionNames?: string[]
runCommand?: string
includeDatabaseUrl: boolean
shutdownTimeout: number
Expand All @@ -99,12 +108,16 @@ class PGLiteServerRunner {
}

static parseConfig(): ServerConfig {
const extensionsArg = args.values.extensions as string | undefined
return {
dbPath: args.values.db as string,
port: parseInt(args.values.port as string, 10),
host: args.values.host as string,
path: args.values.path as string,
debugLevel: parseInt(args.values.debug as string, 10) as DebugLevel,
extensionNames: extensionsArg
? extensionsArg.split(',').map((e) => e.trim())
: undefined,
runCommand: args.values.run as string,
includeDatabaseUrl: args.values['include-database-url'] as boolean,
shutdownTimeout: parseInt(args.values['shutdown-timeout'] as string, 10),
Expand All @@ -126,11 +139,66 @@ class PGLiteServerRunner {
}
}

private async importExtensions(): Promise<Extensions | undefined> {
if (!this.config.extensionNames?.length) {
return undefined
}

const extensions: Extensions = {}

// Built-in extensions that are not in contrib
const builtInExtensions = [
'vector',
'live',
'pg_hashids',
'pg_ivm',
'pg_uuidv7',
'pgtap',
]

for (const name of this.config.extensionNames) {
let ext: Extension | null = null

try {
if (builtInExtensions.includes(name)) {
// Built-in extension (e.g., @electric-sql/pglite/vector)
const mod = await import(`@electric-sql/pglite/${name}`)
ext = mod[name] as Extension
} else {
// Try contrib first (e.g., @electric-sql/pglite/contrib/pgcrypto)
try {
const mod = await import(`@electric-sql/pglite/contrib/${name}`)
ext = mod[name] as Extension
} catch {
// Fall back to external package (e.g., @electric-sql/pglite-<extension>)
const mod = await import(`@electric-sql/pglite-${name}`)
ext = mod[name] as Extension
}
}

if (ext) {
extensions[name] = ext
console.log(`Imported extension: ${name}`)
}
} catch (error) {
console.error(`Failed to import extension '${name}':`, error)
throw new Error(`Failed to import extension '${name}'`)
}
}

return Object.keys(extensions).length > 0 ? extensions : undefined
}

private async initializeDatabase(): Promise<void> {
console.log(`Initializing PGLite with database: ${this.config.dbPath}`)
console.log(`Debug level: ${this.config.debugLevel}`)

this.db = new PGlite(this.config.dbPath, { debug: this.config.debugLevel })
const extensions = await this.importExtensions()

this.db = new PGlite(this.config.dbPath, {
debug: this.config.debugLevel,
extensions,
})
await this.db.waitReady
console.log('PGlite database initialized')
}
Expand Down
1 change: 0 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.