Skip to content

Commit 1a67407

Browse files
authored
feat: switch to a local postgres database for prisma (#49)
1 parent e83cb01 commit 1a67407

File tree

3 files changed

+134
-7
lines changed

3 files changed

+134
-7
lines changed

.github/workflows/ci.yaml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ jobs:
3737

3838
testCli:
3939
runs-on: ubuntu-latest
40+
services:
41+
postgres:
42+
image: postgres
43+
ports:
44+
- 5432:5432
45+
env:
46+
POSTGRES_PASSWORD: postgres
47+
options: >-
48+
--health-cmd pg_isready
49+
--health-interval 10s
50+
--health-timeout 5s
51+
--health-retries 5
52+
env:
53+
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432'
54+
DIRECT_DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432'
55+
AUTH_ORIGIN: http://localhost:3000
56+
AUTH_SECRET: test123
4057
steps:
4158
- uses: actions/checkout@v4
4259

@@ -74,7 +91,7 @@ jobs:
7491

7592
# start prod-app
7693
- name: app:run in prod
77-
run: "export AUTH_ORIGIN=http://localhost:3000 && export AUTH_SECRET=test123 && cd my-sidebase-app && npm run build && timeout 30 npm run preview || ( [[ $? -eq 124 ]] && echo \"app started and did not exit within first 30 seconds, thats good\" )"
94+
run: "cd my-sidebase-app && npm run build && timeout 30 npm run preview || ( [[ $? -eq 124 ]] && echo \"app started and did not exit within first 30 seconds, thats good\" )"
7895

7996
# start dev-app and curl from it
8097
- name: app:test in prod

src/configs/prisma.ts

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { generateModuleHTMLComponent, generateModuleHTMLSnippet } from '../generators/generateModuleComponents'
22
import type { ModuleConfig } from '../types'
3+
import { getUserPkgManager } from '../utils/getUserPkgManager'
34

45
const prismaRootSchema = `// This is your Prisma schema file,
56
// learn more about it in the docs: https://pris.ly/d/prisma-schema
@@ -10,11 +11,15 @@ generator client {
1011
}
1112
1213
datasource db {
13-
// NOTE: You probably want to change this to another database later on
14-
provider = "sqlite"
14+
provider = "postgres"
1515
16-
// This value is read from the .env file.
17-
url = env("DATABASE_URL")
16+
url = env("DATABASE_URL")
17+
18+
// This environment variable can be the same as \`DATABASE_URL\` for non-pglite environments
19+
directUrl = env("DIRECT_DATABASE_URL")
20+
21+
// This is required for development only.
22+
shadowDatabaseUrl = "postgres://postgres@localhost/prisma-shadow?pgbouncer=true&connection_limit=1"
1823
}
1924
`
2025

@@ -25,7 +30,8 @@ const prismaExampleSchema = `model Example {
2530
`
2631

2732
const prismaEnvFile = `# Prisma
28-
DATABASE_URL=file:./db.sqlite
33+
DATABASE_URL="postgres://postgres@localhost:5432/postgres?pgbouncer=true&connection_limit=1"
34+
DIRECT_DATABASE_URL="postgres://postgres@localhost:5432/postgres?connection_limit=1"
2935
`
3036

3137
const prismaExampleEndpoint = `/**
@@ -86,6 +92,84 @@ export function resetDatabase(databaseUrl?: string) {
8692
}
8793
`
8894

95+
const pglite = `/**
96+
* Script that starts a postgres database using pg-gateway (https://github.com/supabase-community/pg-gateway) and pglite (https://github.com/electric-sql/pglite).
97+
*
98+
* We use this database for local development with prisma ORM. The script also supports creating a \`shadow-database\`, which is a second, separate database
99+
* that prisma uses for certain commands, such as \`pnpm prisma migrate dev\`: https://www.prisma.io/docs/orm/prisma-migrate/understanding-prisma-migrate/shadow-database.
100+
*
101+
* To make use of the shadow-database add \`/prisma-shadow\` to the DSN you provide. This script will then spin up a second, in-memory-only database and connect you to it.
102+
*
103+
* This whole script approach is novel to us (before we used sqlite locally). Here is the PR that brought it all together: https://github.com/sidestream-tech/hanselmann-os/pull/3356
104+
*/
105+
import net from 'node:net'
106+
import { unlinkSync, writeFileSync } from 'node:fs'
107+
import { PGlite } from '@electric-sql/pglite'
108+
import { fromNodeSocket } from 'pg-gateway/node'
109+
import { join } from 'pathe'
110+
111+
// If env var is set, we write a file to disk once the server is done starting up. This file can then be used by other processes to check whether the database is ready
112+
const doWriteHealthFile = process.env.WRITE_HEALTH_FILE === 'true'
113+
const HEALTH_FILE_NAME = 'pgliteHealthz'
114+
115+
const db = new PGlite({ dataDir: join(import.meta.dirname, 'pglite-data') })
116+
let activeDb = db
117+
118+
const server = net.createServer(async (socket) => {
119+
activeDb = db
120+
121+
console.info(\`Client connected: \${socket.remoteAddress}:\${socket.remotePort}\`)
122+
await fromNodeSocket(socket, {
123+
serverVersion: '16.3',
124+
125+
auth: {
126+
// No password required
127+
method: 'trust',
128+
},
129+
130+
async onStartup({ clientParams }) {
131+
// create a temp in-memory instance if connecting to the prisma shadow DB
132+
if (clientParams?.database === 'prisma-shadow') {
133+
console.info('Connecting client to shadow database')
134+
activeDb = new PGlite()
135+
}
136+
137+
// Wait for PGlite to be ready before further processing
138+
await activeDb.waitReady
139+
},
140+
141+
// Hook into each client message
142+
async onMessage(data, { isAuthenticated }) {
143+
// Only forward messages to PGlite after authentication
144+
if (!isAuthenticated) {
145+
return
146+
}
147+
148+
// Forward raw message to PGlite and send response to client
149+
return await activeDb.execProtocolRaw(data)
150+
},
151+
})
152+
153+
socket.on('end', () => {
154+
console.info('Client disconnected')
155+
})
156+
})
157+
158+
server.listen(5432, () => {
159+
if (doWriteHealthFile) {
160+
writeFileSync(HEALTH_FILE_NAME, '')
161+
}
162+
163+
console.info('Server listening on port 5432')
164+
})
165+
166+
server.on('close', () => {
167+
if (doWriteHealthFile) {
168+
unlinkSync(HEALTH_FILE_NAME)
169+
}
170+
})
171+
`
172+
89173
const prismaDemoComponent = `<script lang="ts" setup>
90174
const { data: examples } = useFetch('/api/examples')
91175
</script>
@@ -106,7 +190,12 @@ const { data: examples } = useFetch('/api/examples')
106190
const prisma: ModuleConfig = {
107191
humanReadableName: 'Prisma ORM',
108192
description: 'Next-generation Node.js and TypeScript ORM. See more: https://www.prisma.io/',
109-
scripts: [],
193+
scripts: [
194+
{
195+
name: 'db',
196+
command: 'vite-node prisma/pglite.ts',
197+
}
198+
],
110199
dependencies: [
111200
{
112201
name: 'prisma',
@@ -117,6 +206,21 @@ const prisma: ModuleConfig = {
117206
name: '@prisma/client',
118207
version: '^5.18.0',
119208
isDev: false
209+
},
210+
{
211+
name: '@electric-sql/pglite',
212+
version: '^0.2.9',
213+
isDev: true,
214+
},
215+
{
216+
name: 'pg-gateway',
217+
version: '0.3.0-beta.3',
218+
isDev: true,
219+
},
220+
{
221+
name: 'vite-node',
222+
version: '^2.1.1',
223+
isDev: true,
120224
}
121225
],
122226
nuxtConfig: {},
@@ -141,10 +245,15 @@ const prisma: ModuleConfig = {
141245
}, {
142246
path: 'components/Welcome/PrismaDemo.vue',
143247
content: prismaDemoComponent,
248+
}, {
249+
path: 'prisma/pglite.ts',
250+
content: pglite,
144251
}],
145252
tasksPostInstall: [
146253
'- [ ] Prisma: Edit your `prisma/prisma.schema` to your liking',
254+
`- [ ] Prisma: Start your local postgres database using \`${getUserPkgManager()} run db\``,
147255
'- [ ] Prisma: Run `npx prisma db push` to sync the schema to your database & generate the Prisma Client',
256+
'- [ ] Prisma: Add `**/*/pglite-data` and `pgliteHealthz` to your `.gitignore` file'
148257
],
149258
indexVue: generateModuleHTMLSnippet('WelcomePrismaDemo'),
150259
}

src/messages/goodbye.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export function sayGoodbye(preferences: Preferences) {
3030
}
3131

3232
if (preferences.addModules?.includes('prisma') || preferences.setStack === 'cheviot') {
33+
sayCommand(`${packageManager} run db`, 'Start the local postgres database in a new window')
3334
sayCommand('npx prisma db push', 'Initialize the database & Prisma client')
3435
}
3536

0 commit comments

Comments
 (0)