98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-linux (24) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (24) (push) Has been cancelled
Expo App TypeScript typecheck / typecheck (push) Has been cancelled
103 lines
5.5 KiB
Markdown
103 lines
5.5 KiB
Markdown
# 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 for `redis.ping()` health check — zero actual pub/sub usage. `@socket.io/redis-streams-adapter` is a dependency but never imported in source code.
|
|
- **Database**: Prisma with PostgreSQL. `pglite-prisma-adapter` provides a Prisma driver adapter for PGlite. Requires `driverAdapters` preview feature in schema.
|
|
- **File storage**: S3/Minio used for image uploads (avatar uploads via GitHub connect). Used in `uploadImage.ts`, `files.ts`, referenced in `eventRouter.ts`, `accountRoutes.ts`, `type.ts`.
|
|
- **Migrations**: 36 SQL migration files in `prisma/migrations/`. PGlite adapter doesn't support `prisma migrate` CLI, so we apply SQL files directly via PGlite.
|
|
- **Bun+PGlite compile limitation**: There's a [known Bun issue](https://github.com/oven-sh/bun/issues/15032) where PGlite WASM files can't be embedded in `--compile` output. Workaround: copy `postgres.data`/`postgres.wasm` files 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-adapter` to happy-server dependencies
|
|
- [ ] Add `driverAdapters` to `previewFeatures` in `prisma/schema.prisma` generator block
|
|
- [ ] Run `prisma generate` to 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.ts` to conditionally use PGlite when `PGLITE_DIR` env var is set
|
|
- If `PGLITE_DIR` is set: create PGlite instance with that dir, wrap in `PrismaPGlite` adapter, pass to `new PrismaClient({ adapter })`
|
|
- If not set: use existing `new PrismaClient()` (connects via `DATABASE_URL` as before)
|
|
- [ ] Export a `getPGlite()` function for direct SQL access (needed by migration command)
|
|
|
|
### Task 3: Make Redis optional
|
|
- [ ] In `main.ts`, make `redis.ping()` conditional — only if `REDIS_URL` env 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.putObject` with conditional: S3 or `fs.writeFile` to local path
|
|
- Replace `resolveImageUrl` to return local file-serving URL when in local mode
|
|
- [ ] 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.ts` as the portable entry point:
|
|
- Parse `process.argv` for subcommands: `migrate`, `serve`
|
|
- `migrate`: initialize PGlite directly, read all `prisma/migrations/*/migration.sql` files in order, execute them via PGlite SQL, track applied migrations
|
|
- `serve`: call existing `main()` logic
|
|
- No args or `--help`: print usage
|
|
- [ ] Embed migration SQL files at build time (Bun can import text files)
|
|
|
|
### Task 6: Add Bun build configuration
|
|
- [ ] Add `build:standalone` script to `package.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 migrate` creates and migrates a PGlite database
|
|
- [ ] Test: `./happy-server serve` starts 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:**
|
|
```typescript
|
|
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_migrations` table to track applied migrations
|
|
- For each unapplied migration: execute SQL, record in tracking table
|
|
- This mirrors what `prisma migrate deploy` does
|
|
|
|
**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 init` command for first-time setup
|