Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1e67ba1
feat(pglite-socket): WIP socket srver for PGlite
samwillis Apr 12, 2025
e655adb
Add a simple inspectData method to print the io to the console
samwillis Apr 12, 2025
7f64066
Draft tests (failing) and example of how to run
samwillis Apr 12, 2025
496d060
wip
samwillis Apr 12, 2025
74a1520
Flesh out postgres.js tests
samwillis Apr 13, 2025
38c7f45
node-pg tests
samwillis Apr 13, 2025
393494b
Fix stylecheck
samwillis Apr 13, 2025
9f75f84
Add readme
samwillis Apr 13, 2025
3477d97
Draft server cli
samwillis Apr 13, 2025
351f85c
Add building @electric-sql/pg-protocol to ci for building pglite-socket
samwillis Apr 13, 2025
6b9f57b
Change inspect mode to only use the raw method so it doesnt raise errors
samwillis Apr 14, 2025
d3480c0
sync
pmp-p Apr 29, 2025
3de3e4d
sync
pmp-p Apr 29, 2025
dab8b88
sync
pmp-p Apr 29, 2025
5d8c8a3
paths
pmp-p Apr 29, 2025
db35ea1
sync
pmp-p Apr 29, 2025
5782efe
try relative submodule url
pmp-p May 5, 2025
a05d891
sync
pmp-p May 5, 2025
f0eefec
ext compression inside docker
pmp-p May 5, 2025
d46d857
sync
pmp-p May 5, 2025
0337b30
sync subm
pmp-p May 5, 2025
a00d04d
sync
pmp-p May 5, 2025
1e11dfc
sync
pmp-p May 5, 2025
3592411
sync
pmp-p May 5, 2025
3dfe564
test X
pmp-p May 5, 2025
8a6ab26
sync
pmp-p May 6, 2025
2f8dea4
sync
pmp-p May 7, 2025
c5cc236
Merge remote-tracking branch 'origin/main' into samwillis/pglite-socket
pmp-p May 7, 2025
d15a921
use alpha
pmp-p May 8, 2025
ca9ff53
pglite socket + pf2/alpha (#614)
pmp-p May 8, 2025
fbf8ead
Merge remote-tracking branch 'origin/main' into samwillis/pglite-socket
pmp-p May 9, 2025
0f15be2
skip rfq for pipelined PqMsg_Close / PqMsg_CommandComplet
pmp-p May 9, 2025
dc7c0c8
WIP connection queue
samwillis May 9, 2025
5bb7d82
logging
samwillis May 9, 2025
48ea249
Format
samwillis May 9, 2025
6a7d671
disable lint rule
samwillis May 9, 2025
41c3286
update submodule (alpha->main)
pmp-p May 12, 2025
194abea
sdk path
pmp-p May 12, 2025
4225736
sdk path
pmp-p May 12, 2025
25dd608
style buildconfig
pmp-p May 12, 2025
ec25fd0
sync
pmp-p May 12, 2025
eb2d787
remove .raw leftovers
tdrz May 12, 2025
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
3 changes: 3 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build pg-protocol
run: pnpm --filter "@electric-sql/pg-protocol" build

- name: Build packages
run: pnpm --filter="...^pglite" build

Expand Down
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "postgres-pglite"]
path = postgres-pglite
url = https://github.com/electric-sql/postgres-pglite.git
url = ../postgres-pglite.git
224 changes: 224 additions & 0 deletions packages/pglite-socket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# pglite-socket

A socket implementation for PGlite enabling remote connections. This package is a simple wrapper around the `net` module to allow PGlite to be used as a PostgreSQL server.

There are two main components to this package:

- [`PGLiteSocketServer`](#pglitesocketserver) - A TCP server that allows PostgreSQL clients to connect to a PGlite database instance.
- [`PGLiteSocketHandler`](#pglitesockethandler) - A low-level handler for a single socket connection to PGlite. This class handles the raw protocol communication between a socket and PGlite, and can be used to create a custom server.

The package also includes a [CLI](#cli-usage) for quickly starting a PGlite socket server.

Note: As PGlite is a single-connection database, it is not possible to have multiple simultaneous connections open. This means that the socket server will only support a single client connection at a time. While a `PGLiteSocketServer` or `PGLiteSocketHandler` are attached to a PGlite instance they hold an exclusive lock preventing any other connections, or queries on the PGlite instance.

## Installation

```bash
npm install @electric-sql/pglite-socket
# or
yarn add @electric-sql/pglite-socket
# or
pnpm add @electric-sql/pglite-socket
```

## Usage

```typescript
import { PGlite } from '@electric-sql/pglite'
import { PGLiteSocketServer } from '@electric-sql/pglite-socket'

// Create a PGlite instance
const db = await PGlite.create()

// Create and start a socket server
const server = new PGLiteSocketServer({
db,
port: 5432,
host: '127.0.0.1',
})

await server.start()
console.log('Server started on 127.0.0.1:5432')

// Handle graceful shutdown
process.on('SIGINT', async () => {
await server.stop()
await db.close()
console.log('Server stopped and database closed')
process.exit(0)
})
```

## API

### PGLiteSocketServer

Creates a TCP server that allows PostgreSQL clients to connect to a PGlite database instance.

#### Options

- `db: PGlite` - The PGlite database instance
- `port?: number` - The port to listen on (default: 5432)
- `host?: string` - The host to bind to (default: 127.0.0.1)
- `inspect?: boolean` - Print the incoming and outgoing data to the console (default: false)

#### Methods

- `start(): Promise<void>` - Start the socket server
- `stop(): Promise<void>` - Stop the socket server

#### Events

- `listening` - Emitted when the server starts listening
- `connection` - Emitted when a client connects
- `error` - Emitted when an error occurs
- `close` - Emitted when the server is closed

### PGLiteSocketHandler

Low-level handler for a single socket connection to PGlite. This class handles the raw protocol communication between a socket and PGlite.

#### Options

- `db: PGlite` - The PGlite database instance
- `closeOnDetach?: boolean` - Whether to close the socket when detached (default: false)
- `inspect?: boolean` - Print the incoming and outgoing data to the console in hex and ascii (default: false)

#### Methods

- `attach(socket: Socket): Promise<PGLiteSocketHandler>` - Attach a socket to this handler
- `detach(close?: boolean): PGLiteSocketHandler` - Detach the current socket from this handler
- `isAttached: boolean` - Check if a socket is currently attached

#### Events

- `data` - Emitted when data is processed through the handler
- `error` - Emitted when an error occurs
- `close` - Emitted when the socket is closed

#### Example

```typescript
import { PGlite } from '@electric-sql/pglite'
import { PGLiteSocketHandler } from '@electric-sql/pglite-socket'
import { createServer, Socket } from 'net'

// Create a PGlite instance
const db = await PGlite.create()

// Create a handler
const handler = new PGLiteSocketHandler({
db,
closeOnDetach: true,
inspect: false,
})

// Create a server that uses the handler
const server = createServer(async (socket: Socket) => {
try {
await handler.attach(socket)
console.log('Client connected')
} catch (err) {
console.error('Error attaching socket', err)
socket.end()
}
})

server.listen(5432, '127.0.0.1')
```

## Examples

See the [examples directory](./examples) for more usage examples.

## CLI Usage

This package provides a command-line interface for quickly starting a PGlite socket server.

```bash
# Install globally
npm install -g @electric-sql/pglite-socket

# Start a server with default settings (in-memory database, port 5432)
pglite-server

# Start a server with custom options
pglite-server --db=/path/to/database --port=5433 --host=0.0.0.0 --debug=1

# Using short options
pglite-server -d /path/to/database -p 5433 -h 0.0.0.0 -v 1

# Show help
pglite-server --help
```

### CLI Options

- `-d, --db=PATH` - Database path (default: memory://)
- `-p, --port=PORT` - Port to listen on (default: 5432)
- `-h, --host=HOST` - Host to bind to (default: 127.0.0.1)
- `-v, --debug=LEVEL` - Debug level 0-5 (default: 0)

### Using in npm scripts

You can add the CLI to your package.json scripts for convenient execution:

```json
{
"scripts": {
"db:start": "pglite-server --db=./data/mydb --port=5433",
"db:dev": "pglite-server --db=memory:// --debug=1"
}
}
```

Then run with:

```bash
npm run db:start
# or
npm run db:dev
```

### Connecting to the server

Once the server is running, you can connect to it using any PostgreSQL client:

#### Using psql

```bash
psql -h localhost -p 5432 -d template1
```

#### Using Node.js clients

```javascript
// Using node-postgres
import pg from 'pg'
const client = new pg.Client({
host: 'localhost',
port: 5432,
database: 'template1'
})
await client.connect()

// Using postgres.js
import postgres from 'postgres'
const sql = postgres({
host: 'localhost',
port: 5432,
database: 'template1'
})
```

### Limitations and Tips

- Remember that PGlite only supports one connection at a time. If you're unable to connect, make sure no other client is currently connected.
- For development purposes, using an in-memory database (`--db=memory://`) is fastest but data won't persist after the server is stopped.
- For persistent storage, specify a file path for the database (e.g., `--db=./data/mydb`).
- When using debug mode (`--debug=1` or higher), additional protocol information will be displayed in the console.
- To allow connections from other machines, set the host to `0.0.0.0` with `--host=0.0.0.0`.

## License

Apache 2.0
29 changes: 29 additions & 0 deletions packages/pglite-socket/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import globals from 'globals'
import rootConfig from '../../eslint.config.js'

export default [
...rootConfig,
{
ignores: ['release/**/*', 'examples/**/*', 'dist/**/*'],
},
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
rules: {
...rootConfig.rules,
'@typescript-eslint/no-explicit-any': 'off',
},
},
{
files: ['tests/targets/deno/**/*.js'],
languageOptions: {
globals: {
Deno: false,
},
},
},
]
63 changes: 63 additions & 0 deletions packages/pglite-socket/examples/basic-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { PGLiteSocketServer } from '../src'
import { PGlite, DebugLevel } from '@electric-sql/pglite'

/*
* This is a basic example of how to use the PGLiteSocketServer class.
* It creates a PGlite instance and a PGLiteSocketServer instance and starts the server.
* It also handles SIGINT to stop the server and close the database.
* You can run this example with the following command:
*
* ```bash
* pnpm tsx examples/basic-server.ts
* ```
* or with the handy script:
* ```bash
* pnpm example:basic-server
* ```
*
* You can set the host and port with the following environment variables:
*
* ```bash
* HOST=127.0.0.1 PORT=5432 DEBUG=1 pnpm tsx examples/basic-server.ts
* ```
*
* Debug level can be set to 0, 1, 2, 3, or 4.
*
* ```bash
* DEBUG=1 pnpm tsx examples/basic-server.ts
* ```
*/

const PORT = process.env.PORT ? parseInt(process.env.PORT) : 5432
const HOST = process.env.HOST ?? '127.0.0.1'
const DEBUG = process.env.DEBUG
? (parseInt(process.env.DEBUG) as DebugLevel)
: 0

// Create a PGlite instance
const db = await PGlite.create({
debug: DEBUG,
})

// Check if the database is working
console.log(await db.query('SELECT version()'))

// Create a PGLiteSocketServer instance
const server = new PGLiteSocketServer({
db,
port: PORT,
host: HOST,
inspect: !!DEBUG, // Print the incoming and outgoing data to the console
})

// Start the server
await server.start()
console.log(`Server started on ${HOST}:${PORT}`)

// Handle SIGINT to stop the server and close the database
process.on('SIGINT', async () => {
await server.stop()
await db.close()
console.log('Server stopped and database closed')
process.exit(0)
})
48 changes: 48 additions & 0 deletions packages/pglite-socket/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@electric-sql/pglite-socket",
"version": "0.0.1",
"description": "A socket implementation for PGlite enabling remote connections",
"author": "Electric DB Limited",
"homepage": "https://pglite.dev",
"license": "Apache-2.0",
"keywords": [
"postgres",
"sql",
"database",
"wasm",
"pglite",
"socket"
],
"private": false,
"publishConfig": {
"access": "public"
},
"type": "module",
"main": "index.js",
"bin": {
"pglite-server": "./dist/scripts/server.js"
},
"scripts": {
"build": "tsup",
"lint": "eslint ./src ./tests --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write ./src ./tests",
"typecheck": "tsc",
"stylecheck": "pnpm lint && prettier --check ./src ./tests",
"test": "vitest",
"example:basic-server": "tsx examples/basic-server.ts",
"pglite-server:dev": "tsx --watch src/scripts/server.ts"
},
"devDependencies": {
"@electric-sql/pglite": "workspace:*",
"@electric-sql/pg-protocol": "workspace:*",
"@types/emscripten": "^1.39.13",
"@types/node": "^20.16.11",
"pg": "^8.14.0",
"postgres": "^3.4.5",
"tsx": "^4.19.2",
"vitest": "^1.3.1"
},
"peerDependencies": {
"@electric-sql/pglite": "workspace:*"
}
}
Loading
Loading