98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Waiting to run
CLI Smoke Test / smoke-test-linux (24) (push) Waiting to run
CLI Smoke Test / smoke-test-windows (20) (push) Waiting to run
CLI Smoke Test / smoke-test-windows (24) (push) Waiting to run
Expo App TypeScript typecheck / typecheck (push) Waiting to run
5.5 KiB
5.5 KiB
Portable Single-Binary Distribution
Overview
Create a portable, self-contained distribution of happy-server as a single Bun-compiled binary. It runs without Redis (already has in-memory event bus), uses PGlite for embedded PostgreSQL, and local filesystem for file storage. CLI provides happy-server migrate and happy-server serve commands.
Context
- Event bus: Already 100% in-memory (
eventRouter.ts). Redis is only used forredis.ping()health check — zero actual pub/sub usage.@socket.io/redis-streams-adapteris a dependency but never imported in source code. - Database: Prisma with PostgreSQL.
pglite-prisma-adapterprovides a Prisma driver adapter for PGlite. RequiresdriverAdapterspreview feature in schema. - File storage: S3/Minio used for image uploads (avatar uploads via GitHub connect). Used in
uploadImage.ts,files.ts, referenced ineventRouter.ts,accountRoutes.ts,type.ts. - Migrations: 36 SQL migration files in
prisma/migrations/. PGlite adapter doesn't supportprisma migrateCLI, so we apply SQL files directly via PGlite. - Bun+PGlite compile limitation: There's a known Bun issue where PGlite WASM files can't be embedded in
--compileoutput. Workaround: copypostgres.data/postgres.wasmfiles next to binary.
Development Approach
- Complete each task fully before moving to the next
- Make small, focused changes
- Minimal changes to existing code — prefer conditional paths over rewrites
- Test by running the standalone binary after build
Implementation Steps
Task 1: Add PGlite + adapter dependencies
- Add
@electric-sql/pglite,pglite-prisma-adapterto happy-server dependencies - Add
driverAdapterstopreviewFeaturesinprisma/schema.prismagenerator block - Run
prisma generateto regenerate client with adapter support - Verify existing server still works (no breaking changes from preview feature)
Task 2: Make database layer PGlite-aware
- Modify
sources/storage/db.tsto conditionally use PGlite whenPGLITE_DIRenv var is set- If
PGLITE_DIRis set: create PGlite instance with that dir, wrap inPrismaPGliteadapter, pass tonew PrismaClient({ adapter }) - If not set: use existing
new PrismaClient()(connects viaDATABASE_URLas before)
- If
- Export a
getPGlite()function for direct SQL access (needed by migration command)
Task 3: Make Redis optional
- In
main.ts, makeredis.ping()conditional — only ifREDIS_URLenv var is set - Skip redis import when not needed (dynamic import or guard)
Task 4: Replace S3 with local filesystem storage
- Modify
sources/storage/files.ts:- If S3 env vars are set: use existing Minio client (no change)
- If not: use local filesystem under
DATA_DIR/files/directory
- Modify
sources/storage/uploadImage.ts:- Replace
s3client.putObjectwith conditional: S3 orfs.writeFileto local path - Replace
resolveImageUrlto return local file-serving URL when in local mode
- Replace
- Add a static file serving route in API for local files (e.g.,
/files/*)
Task 5: Create CLI entry point with migrate command
- Create
sources/standalone.tsas the portable entry point:- Parse
process.argvfor subcommands:migrate,serve migrate: initialize PGlite directly, read allprisma/migrations/*/migration.sqlfiles in order, execute them via PGlite SQL, track applied migrationsserve: call existingmain()logic- No args or
--help: print usage
- Parse
- Embed migration SQL files at build time (Bun can import text files)
Task 6: Add Bun build configuration
- Add
build:standalonescript topackage.json:bun build ./sources/standalone.ts --compile --outfile happy-server - Handle PGlite WASM files: add a post-build step to copy postgres data files next to the binary
- Test the build:
bun run build:standalone - Test:
./happy-server migratecreates and migrates a PGlite database - Test:
./happy-server servestarts the server with PGlite
Task 7: Verify end-to-end
- Build the binary
- Run
./happy-server migrate— verify database is created in./data/ - Run
./happy-server serve— verify server starts, health endpoint responds - Verify no Redis connection attempted
- Verify files can be stored/served locally
Technical Details
PGlite initialization:
import { PGlite } from '@electric-sql/pglite';
import { PrismaPGlite } from 'pglite-prisma-adapter';
const pglite = new PGlite(process.env.PGLITE_DIR || './data/pglite');
const adapter = new PrismaPGlite(pglite);
const db = new PrismaClient({ adapter });
Migration approach:
- Read SQL files from
prisma/migrations/sorted by directory name (timestamp order) - Create a
_prisma_migrationstable to track applied migrations - For each unapplied migration: execute SQL, record in tracking table
- This mirrors what
prisma migrate deploydoes
Data directory structure:
./data/ (or $DATA_DIR)
pglite/ # PGlite database files
files/ # Uploaded files (replaces S3)
public/users/... # Same path structure as S3
CLI usage:
happy-server migrate # Apply database migrations
happy-server serve # Start the server
Post-Completion
- Document env vars for portable mode (
PGLITE_DIR,DATA_DIR,HANDY_MASTER_SECRET) - Test on Linux for cross-platform binary (Bun cross-compile)
- Consider adding
happy-server initcommand for first-time setup