Skip to content

Commit c509fac

Browse files
committed
feat: db-service poc
1 parent 9f717a9 commit c509fac

File tree

89 files changed

+644
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+644
-5
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ build
1919
# misc
2020
.DS_Store
2121
*.pem
22+
*.srl
2223

2324
# debug
2425
npm-debug.log*
@@ -34,3 +35,5 @@ yarn-error.log*
3435
# typescript
3536
*.tsbuildinfo
3637
next-env.d.ts
38+
39+
dbs/

apps/db-service/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "db-service",
3+
"scripts": {
4+
"dev": "tsx src/index.ts",
5+
"generate:certs": "scripts/generate-certs.sh",
6+
"psql": "psql 'host=localhost port=5432 user=postgres sslmode=verify-ca sslrootcert=ca-cert.pem'"
7+
},
8+
"dependencies": {
9+
"pg-gateway": "*"
10+
},
11+
"devDependencies": {
12+
"@electric-sql/pglite": "npm:@gregnr/pglite@0.2.0-dev.8",
13+
"@types/node": "^20.14.11",
14+
"tsx": "^4.16.2",
15+
"typescript": "^5.5.3"
16+
}
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
openssl genpkey -algorithm RSA -out ca-key.pem
4+
openssl req -new -x509 -key ca-key.pem -out ca-cert.pem -days 365 -subj "/CN=MyCA"
5+
6+
openssl genpkey -algorithm RSA -out server-key.pem
7+
openssl req -new -key server-key.pem -out server-csr.pem -subj "/CN=*.db.example.com"
8+
9+
openssl x509 -req -in server-csr.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365

apps/db-service/src/index.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { PGlite, PGliteInterface } from '@electric-sql/pglite'
2+
import { mkdir, readFile } from 'node:fs/promises'
3+
import net from 'node:net'
4+
import { PostgresConnection, TlsOptionsCallback, hashMd5Password } from 'pg-gateway'
5+
6+
const tls: TlsOptionsCallback = async ({ sniServerName }) => {
7+
// Optionally serve different certs based on `sniServerName`
8+
// In this example we'll use a single wildcard cert for all servers (ie. *.db.example.com)
9+
return {
10+
key: await readFile('server-key.pem'),
11+
cert: await readFile('server-cert.pem'),
12+
ca: await readFile('ca-cert.pem'),
13+
}
14+
}
15+
16+
function getIdFromServerName(serverName: string) {
17+
// The left-most subdomain contains the ID
18+
// ie. 12345.db.example.com -> 12345
19+
const [id] = serverName.split('.')
20+
return id
21+
}
22+
23+
const server = net.createServer((socket) => {
24+
let db: PGliteInterface
25+
26+
const connection = new PostgresConnection(socket, {
27+
serverVersion: '16.3 (PGlite 0.2.0)',
28+
authMode: 'md5Password',
29+
tls,
30+
async validateCredentials(credentials) {
31+
if (credentials.authMode === 'md5Password') {
32+
const { hash, salt } = credentials
33+
const expectedHash = await hashMd5Password('postgres', 'postgres', salt)
34+
return hash === expectedHash
35+
}
36+
return false
37+
},
38+
async onTlsUpgrade({ tlsInfo }) {
39+
if (!tlsInfo) {
40+
connection.sendError({
41+
severity: 'FATAL',
42+
code: '08000',
43+
message: `ssl connection required`,
44+
})
45+
connection.socket.end()
46+
return
47+
}
48+
49+
if (!tlsInfo.sniServerName) {
50+
connection.sendError({
51+
severity: 'FATAL',
52+
code: '08000',
53+
message: `ssl sni extension required`,
54+
})
55+
connection.socket.end()
56+
return
57+
}
58+
59+
const databaseId = getIdFromServerName(tlsInfo.sniServerName)
60+
61+
db = new PGlite(`./dbs/${databaseId}`)
62+
},
63+
async onStartup() {
64+
// Wait for PGlite to be ready before further processing
65+
await db.waitReady
66+
return false
67+
},
68+
async onMessage(data, { isAuthenticated }) {
69+
// Only forward messages to PGlite after authentication
70+
if (!isAuthenticated) {
71+
return false
72+
}
73+
74+
// Forward raw message to PGlite
75+
try {
76+
const responseData = await db.execProtocolRaw(data)
77+
connection.sendData(responseData)
78+
} catch (err) {
79+
console.error(err)
80+
}
81+
return true
82+
},
83+
})
84+
85+
socket.on('end', () => {
86+
console.log('Client disconnected')
87+
})
88+
})
89+
90+
server.listen(5432, async () => {
91+
console.log('Server listening on port 5432')
92+
93+
await mkdir('./dbs', { recursive: true })
94+
})
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)