@@ -12,7 +12,7 @@ Build a single-user AT Protocol Personal Data Server (PDS) on Cloudflare Workers
1212
1313** Live at: https://pds.mk.gg **
1414
15- ### Completed (Phase 1 + Phase 2 + Phase 3 + Phase 4 + Phase 6 + Phase 7)
15+ ### Completed (Phase 1 + Phase 2 + Phase 3 + Phase 4 + Phase 5 + Phase 6 + Phase 7)
1616
1717- ✅ ** Storage Layer** (Phase 1) - ` SqliteRepoStorage ` implementing ` @atproto/repo ` RepoStorage interface
1818- ✅ ** Durable Object** (Phase 2) - ` AccountDurableObject ` with Repo integration
@@ -41,17 +41,25 @@ Build a single-user AT Protocol Personal Data Server (PDS) on Cloudflare Workers
4141- ✅ ** Deployment** - Custom domain ` pds.mk.gg ` with auto-provisioned DNS
4242- ✅ ** Signing Keys** - secp256k1 keypair generated and configured
4343- ✅ ** Environment Validation** - Module-scope validation using ` cloudflare:workers ` env import
44- - ✅ ** Testing** - Migrated to vitest 4, all 48 tests passing
44+ - ✅ ** Blob Storage** (Phase 5) - R2 integration for image/media uploads
45+ - ` BlobStore ` class using ` cidForRawBytes() ` from ` @atproto/lex-cbor `
46+ - ` com.atproto.repo.uploadBlob ` endpoint (authenticated, 5MB limit)
47+ - ` com.atproto.sync.getBlob ` endpoint (public read access)
48+ - Direct R2 access in endpoint (R2ObjectBody cannot be serialized across RPC)
49+ - Blobs stored with DID prefix for isolation
50+ - ✅ ** Testing** - Migrated to vitest 4, all 58 tests passing
4551 - 16 storage tests
4652 - 26 XRPC tests (auth, concurrency, error handling, CAR validation)
4753 - 6 firehose tests (event sequencing, cursor validation, backfill)
54+ - 10 blob tests (upload, retrieval, size limits, content types)
4855- ✅ ** TypeScript** - All diagnostic errors resolved, proper type declarations for cloudflare: test
4956- ✅ ** Protocol Helpers** - All protocol operations use official @atproto utilities
5057 - Record keys: ` TID.nextStr() ` from ` @atproto/common-web `
5158 - AT URI construction: ` AtUri.make() ` from ` @atproto/syntax `
5259 - DID validation: ` ensureValidDid() ` from ` @atproto/syntax `
5360 - Handle validation: ` ensureValidHandle() ` from ` @atproto/syntax `
5461 - CBOR encoding: ` @atproto/lex-cbor `
62+ - Blob CID generation: ` cidForRawBytes() ` from ` @atproto/lex-cbor `
5563 - CAR export: ` blocksToCarFile() ` from ` @atproto/repo `
5664- ✅ ** Dependency Optimization** - Removed 6 low-level dependencies, added 3 @atproto helpers
5765 - Removed: ` varint ` , ` @types/varint ` , ` cborg ` , ` uint8arrays ` , ` @ipld/dag-cbor ` , ` multiformats `
@@ -60,7 +68,7 @@ Build a single-user AT Protocol Personal Data Server (PDS) on Cloudflare Workers
6068
6169### Not Started
6270
63- - ⬜ ** Blob Storage ** (Phase 5) - R2 integration (R2 needs enabling in dashboard)
71+ - None! All planned phases are complete.
6472
6573### Testing & Development Notes
6674
@@ -89,6 +97,8 @@ for (const [cidStr, bytes] of internalMap) { ... }
8997
9098** Durable Object RPC Types** : Using ` Rpc.Serializable<any> ` for RPC method return types to ensure TypeScript correctly infers serializable types instead of ` never ` .
9199
100+ ** R2 Blob Retrieval** : The ` getBlob ` endpoint accesses R2 directly rather than going through DO RPC because ` R2ObjectBody ` cannot be serialized (contains ReadableStream). Upload operations still use DO RPC since they only need to pass Uint8Array and return serializable metadata.
101+
92102---
93103
94104## Architecture
@@ -1720,6 +1730,125 @@ These can all be added later.
17201730
17211731---
17221732
1733+ ## Deployment Architecture
1734+
1735+ ### Design Decision: Zero-Code Re-Export Pattern
1736+
1737+ For maximum simplicity, users deploying a PDS should not need to write any code. The ` @ascorbic/pds-worker ` package provides everything needed, and users simply re-export it.
1738+
1739+ #### User's Worker (Minimal)
1740+
1741+ ``` typescript
1742+ // src/index.ts
1743+ export { default , AccountDurableObject } from ' @ascorbic/pds-worker'
1744+ ```
1745+
1746+ That's it. No additional code required.
1747+
1748+ #### Package Exports
1749+
1750+ The ` @ascorbic/pds-worker ` package exports:
1751+
1752+ ``` typescript
1753+ // Core exports for advanced users
1754+ export { SqliteRepoStorage } from " ./storage"
1755+ export { AccountDurableObject } from " ./account-do"
1756+ export { BlobStore , type BlobRef } from " ./blobs"
1757+ export { Sequencer } from " ./sequencer"
1758+
1759+ // Default export: configured Hono app
1760+ export default app
1761+ ```
1762+
1763+ #### Configuration
1764+
1765+ All configuration is via environment variables and secrets:
1766+
1767+ ** Required environment variables:**
1768+ - ` PDS_HOSTNAME ` - Public hostname (set in wrangler.jsonc)
1769+
1770+ ** Required secrets:**
1771+ - ` DID ` - Account's DID (e.g., "did:web: pds .example.com")
1772+ - ` HANDLE ` - Account's handle (e.g., "alice.pds.example.com")
1773+ - ` AUTH_TOKEN ` - Bearer token for write operations
1774+ - ` SIGNING_KEY ` - Private key for signing commits (secp256k1 JWK)
1775+ - ` SIGNING_KEY_PUBLIC ` - Public key for DID document (multibase)
1776+
1777+ ** Resource bindings:**
1778+ - ` ACCOUNT ` - DurableObjectNamespace binding
1779+ - ` BLOBS ` - R2Bucket binding
1780+
1781+ #### Deployment Workflow
1782+
1783+ 1 . ** Scaffold** (future: via ` npm create @ascorbic/pds ` )
1784+ - Creates project directory with re-export pattern
1785+ - Generates wrangler.jsonc with bindings
1786+ - Provides setup script for key generation
1787+
1788+ 2 . ** Setup** (via setup script)
1789+ - Interactive prompts for hostname and handle
1790+ - Generates secp256k1 keypair
1791+ - Creates DID (did: web based on hostname)
1792+ - Generates random AUTH_TOKEN
1793+ - Writes to ` .dev.vars ` for local dev
1794+
1795+ 3 . ** Local Development**
1796+ ``` bash
1797+ wrangler dev
1798+ ```
1799+
1800+ 4 . ** Production Deployment**
1801+ ``` bash
1802+ # Create R2 bucket
1803+ wrangler r2 bucket create pds-blobs
1804+
1805+ # Set secrets
1806+ wrangler secret put DID
1807+ wrangler secret put HANDLE
1808+ wrangler secret put AUTH_TOKEN
1809+ wrangler secret put SIGNING_KEY
1810+ wrangler secret put SIGNING_KEY_PUBLIC
1811+
1812+ # Deploy
1813+ wrangler deploy
1814+ ```
1815+
1816+ #### Demo Structure
1817+
1818+ ```
1819+ demos/pds/
1820+ ├── src/
1821+ │ └── index.ts # Re-exports @ascorbic/pds-worker
1822+ ├── wrangler.jsonc # Worker config with bindings
1823+ ├── package.json # Dependencies
1824+ ├── .env.example # Template for required vars
1825+ └── README.md # Setup instructions
1826+ ```
1827+
1828+ #### Future: create-pds CLI
1829+
1830+ ``` bash
1831+ npm create @ascorbic/pds my-pds
1832+ cd my-pds
1833+ npm install
1834+ npm run dev
1835+ ```
1836+
1837+ This will scaffold a complete deployment with:
1838+ - Project structure
1839+ - Generated keys and configuration
1840+ - Pre-configured wrangler.jsonc
1841+ - Setup instructions
1842+
1843+ #### Rationale
1844+
1845+ 1 . ** Single-user PDS** : Not a multi-tenant platform - each deployment serves one account
1846+ 2 . ** Configuration via environment** : All customization is environment-based
1847+ 3 . ** No code needed** : Users shouldn't need to understand Hono/Workers/DOs to deploy
1848+ 4 . ** Future-proof** : Can add factory function later for customization without breaking changes
1849+
1850+ ---
1851+
17231852## Reference Material
17241853
17251854- AT Protocol specs: https://atproto.com/specs
0 commit comments