# pds.js A minimal AT Protocol Personal Data Server written in JavaScript. > **Work in progress** - This is experimental. You probably shouldn't use this yet. ## Features - Repo operations (createRecord, getRecord, putRecord, deleteRecord, applyWrites, listRecords) - Sync endpoints (getRepo, getRecord, subscribeRepos, listRepos, getLatestCommit) - Auth (createSession, getSession, refreshSession) - OAuth 2.0 (PAR, authorization code + PKCE, DPoP-bound tokens, refresh, revoke) - Handle resolution (resolveHandle) - AppView proxy (app.bsky.* forwarding with service auth) - Relay notification (requestCrawl) - Blob storage (uploadBlob, getBlob, listBlobs) - Single-tenant (multi-tenant hosting on roadmap) See [endpoint comparison](docs/endpoint-comparison.md) for detailed coverage vs the official atproto PDS. **Platform options:** - **Node.js** - Simple setup, filesystem storage, ideal for self-hosting - **Deno** - Modern runtime with built-in TypeScript, uses node:sqlite - **Cloudflare Workers** - Edge deployment with Durable Objects and R2 ## Quick Start (Local Development) ```bash git clone https://tangled.org/chadtmiller.com/pds.js cd pds.js && npm install # Start local PLC + relay (requires Docker) docker compose up -d # Start PDS npm run dev:node # Register with local PLC (in another terminal) npm run setup -- --pds https://localhost:3443 --plc-url http://localhost:2582 --relay-url http://localhost:2470 ``` ## Configuration | Variable | Required | Description | |----------|----------|-------------| | PDS_PASSWORD | Yes | Password for legacy auth and OAuth consent | | JWT_SECRET | Yes | Secret for signing JWTs | | PDS_DB_PATH | No | SQLite database path (default: ./pds.db) | | PDS_BLOBS_DIR | No | Blob storage directory (default: ./blobs) | | PORT | No | Server port (default: 3000) | | HOSTNAME | No | Public hostname for the PDS | | APPVIEW_URL | No | AppView URL for proxying | | APPVIEW_DID | No | AppView DID for service auth | | RELAY_URL | No | Relay URL for firehose notifications | ## Deploy: Node.js 1. **Install dependencies** ```bash npm install ``` 2. **Configure environment** ```bash cp .env.example .env # Edit .env with your values ``` 3. **Start server** ```bash # Development (auto-reload) npm run dev:node # Production cd examples/node && npm start ``` 4. **Initialize PDS** (only after deploying to a public domain) ```bash npm run setup -- --pds https://your-hostname.com ``` > **Note:** This registers your DID with the production PLC directory. Only run this once your PDS is accessible at a public URL. **Production notes:** - Use a reverse proxy (nginx, Caddy) for TLS termination - Set `HOSTNAME` to your public domain - SQLite database and blobs are stored locally by default ## Deploy: Cloudflare Workers **Prerequisites:** - Cloudflare account with Workers Paid plan (for Durable Objects) - Wrangler CLI installed 1. **Create R2 bucket** ```bash wrangler r2 bucket create pds-blobs ``` 2. **Create KV namespace** ```bash wrangler kv namespace create SHARED_KV ``` 3. **Configure wrangler.toml** Update `examples/cloudflare/wrangler.toml` with your KV namespace ID from step 2. 4. **Set secrets** ```bash wrangler secret put PDS_PASSWORD wrangler secret put JWT_SECRET ``` 5. **Deploy** ```bash cd examples/cloudflare wrangler deploy ``` 6. **Initialize PDS** ```bash npm run setup -- --pds https://your-worker.workers.dev ``` ## Deploy: Deno Requires Deno 2.2+ for `node:sqlite` support. ```bash cd examples/deno deno run --allow-net --allow-read --allow-write --allow-env main.ts ``` See [examples/deno/README.md](examples/deno/README.md) for configuration options. ## Testing ```bash npm test # Unit tests npm run test:e2e:node # E2E against Node.js npm run test:e2e:deno # E2E against Deno npm run test:e2e:cloudflare # E2E against Cloudflare npm run test:coverage # Coverage report ``` ## Architecture pds.js uses hexagonal architecture with platform-agnostic ports: ``` ┌─────────────────────────────────────┐ │ @pds/core │ │ (business logic, XRPC handlers) │ └──────────────┬──────────────────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ ActorStoragePort│ │ SharedStoragePort│ │ BlobPort │ │ (per-user data)│ │ (global data) │ │ (binary storage)│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ ┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ┌───────┐ ┌─────────┐ ┌─────────┐ ┌─────┐ ┌─────┐ │SQLite │ │ Durable │ │ KV │ │ FS │ │ R2 │ │ │ │ Objects │ │ │ │ │ │ │ └───────┘ └─────────┘ └─────────┘ └─────┘ └─────┘ Node.js Cloudflare Cloudflare Node.js Cloudflare ``` **Ports:** - **ActorStoragePort** - Per-user data (repo, preferences, OAuth tokens) - **SharedStoragePort** - Global data (handle resolution, DID mappings) - **BlobPort** - Binary storage (images, videos) - **WebSocketPort** - Real-time subscriptions (subscribeRepos) ## Packages | Package | Description | |---------|-------------| | @pds/core | Platform-agnostic business logic and XRPC handlers | | @pds/node | Node.js HTTP server with WebSocket support | | @pds/deno | Deno HTTP server with WebSocket support | | @pds/cloudflare | Cloudflare Workers entry point with Durable Objects | | @pds/storage-sqlite | SQLite storage adapter (better-sqlite3 or node:sqlite) | | @pds/blobs-fs | Filesystem blob storage for Node.js | | @pds/blobs-deno | Filesystem blob storage for Deno | | @pds/blobs-s3 | S3-compatible blob storage | **Node.js usage:** ```javascript import { createServer } from '@pds/node' const { listen } = await createServer({ dbPath: './pds.db', blobsDir: './blobs', jwtSecret: process.env.JWT_SECRET, port: 3000, }) await listen() ``` **Cloudflare usage:** ```javascript // Re-export from @pds/cloudflare (or point wrangler.toml directly at it) export { default, PDSDurableObject } from '@pds/cloudflare' ``` **Deno usage:** ```typescript import { createServer } from '@pds/deno' const { listen } = await createServer({ dbPath: './pds.db', blobsDir: './blobs', jwtSecret: Deno.env.get('JWT_SECRET'), port: 3000, }) await listen() ``` ## Contributing **Before submitting a PR:** ```bash npm run check # Biome lint + format check npm run typecheck # TypeScript npm test # Unit tests ``` **Development commands:** ```bash npm run dev:node # Run Node.js dev server npm run dev:cloudflare # Run Cloudflare dev server npm run format # Auto-format code npm run lint # Run linter ``` **Project structure:** ``` packages/ core/ # Platform-agnostic core node/ # Node.js adapter deno/ # Deno adapter cloudflare/ # Cloudflare adapter storage-*/ # Storage implementations blobs-*/ # Blob storage implementations examples/ node/ # Node.js example deno/ # Deno example cloudflare/ # Cloudflare example ```