@getcirrus/pds 0.2.2 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  <div align="center">
2
- <h1>☁️ Cirrus</h1>
3
- <p><em>The lightest PDS in the Atmosphere</em></p>
2
+ <h1>☁️</h1>
3
+ <h1><samp>CIRRUS</samp></h1>
4
+ <p><em>The lightest PDS in the Atmosphere</em></p>
4
5
  </div>
5
6
 
6
7
  Cirrus is a single-user [AT Protocol](https://atproto.com) Personal Data Server (PDS) that runs on Cloudflare Workers. Named for the highest, lightest clouds in a blue sky – fitting for a Bluesky server running on Cloudflare.
package/dist/index.js CHANGED
@@ -6077,7 +6077,7 @@ async function resetMigration(c, accountDO) {
6077
6077
 
6078
6078
  //#endregion
6079
6079
  //#region package.json
6080
- var version = "0.2.2";
6080
+ var version = "0.2.5";
6081
6081
 
6082
6082
  //#endregion
6083
6083
  //#region src/index.ts
@@ -6157,6 +6157,45 @@ app.get("/health", (c) => c.json({
6157
6157
  status: "ok",
6158
6158
  version
6159
6159
  }));
6160
+ app.get("/", (c) => {
6161
+ const html = `<!DOCTYPE html>
6162
+ <html lang="en">
6163
+ <head>
6164
+ <meta charset="utf-8">
6165
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6166
+ <title>☁️</title>
6167
+ <style>
6168
+ * { margin: 0; padding: 0; box-sizing: border-box; }
6169
+ body {
6170
+ min-height: 100vh;
6171
+ display: flex;
6172
+ flex-direction: column;
6173
+ justify-content: center;
6174
+ align-items: center;
6175
+ font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
6176
+ background: #f0f0f0;
6177
+ color: #000;
6178
+ padding: 2rem;
6179
+ }
6180
+ .cloud { font-size: clamp(4rem, 15vw, 10rem); line-height: 1; }
6181
+ .name { font-size: clamp(1.5rem, 5vw, 3rem); font-weight: 700; letter-spacing: 0.2em; margin: 1rem 0; }
6182
+ .what { font-size: clamp(0.8rem, 2vw, 1rem); color: #666; max-width: 300px; text-align: center; }
6183
+ .handle { font-size: clamp(0.9rem, 2.5vw, 1.2rem); margin-top: 2rem; padding: 0.5rem 1rem; border: 2px solid #000; }
6184
+ :is(.handle, .name) a { color: inherit; text-decoration: none; }
6185
+ :is(.handle, .name) a:hover { text-decoration: underline; }
6186
+ .version { position: fixed; bottom: 1rem; right: 1rem; font-size: 0.7rem; color: #999; }
6187
+ </style>
6188
+ </head>
6189
+ <body>
6190
+ <div class="cloud">☁️</div>
6191
+ <div class="name"><a href="https://github.com/ascorbic/cirrus">CIRRUS</a></div>
6192
+ <div class="what">a personal data server for the atmosphere</div>
6193
+ <div class="handle"><a href="https://bsky.app/profile/${c.env.HANDLE}" target="_blank">@${c.env.HANDLE}</a></div>
6194
+ <div class="version">v${version}</div>
6195
+ </body>
6196
+ </html>`;
6197
+ return c.html(html);
6198
+ });
6160
6199
  app.get("/xrpc/com.atproto.sync.getRepo", (c) => getRepo(c, getAccountDO(c.env)));
6161
6200
  app.get("/xrpc/com.atproto.sync.getRepoStatus", (c) => getRepoStatus(c, getAccountDO(c.env)));
6162
6201
  app.get("/xrpc/com.atproto.sync.getBlocks", (c) => getBlocks(c, getAccountDO(c.env)));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["sql: SqlStorage","missing: CID[]","blobs: Array<{ cid: string; recordUri: string }>","sql: SqlStorage","sql: SqlStorage","eventPayload: Omit<CommitEvent, \"seq\">","cborEncode","cborDecode","r2: R2Bucket","did: string","env","collections: string[]","createOp: RecordCreateOp","commitData: CommitData","deleteOp: RecordDeleteOp","op: RecordWriteOp","ops: RecordWriteOp[]","results: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t\tcollection: string;\n\t\t\trkey: string;\n\t\t\taction: WriteOpAction;\n\t\t}>","op: RecordCreateOp","op: RecordUpdateOp","op: RecordDeleteOp","finalResults: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t}>","opsWithCids: Array<RecordWriteOp & { cid?: CID | null }>","cborEncode","result: Record<string, unknown>","cids: string[]","cachedKeypair: Secp256k1Keypair | null","cachedSigningKey: string | null","result: Partial<T>","payload: ServiceJwtPayload","accountDO: DurableObjectStub<AccountDurableObject>","getAccountDO","env","getAccountDO: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>","token: string | undefined","audienceDid: string","targetUrl: URL","didResolver","headers: Record<string, string>","userDid: string | undefined","getKeypair","reqInit: RequestInit","result: { cids: string[]; cursor?: string }","verifyPassword","env","_env","keypairPromise: Promise<Secp256k1Keypair> | null","id","didDocument","sync.getRepo","sync.getRepoStatus","sync.getBlocks","sync.getBlob","sync.listRepos","sync.listBlobs","repo.describeRepo","repo.getRecord","repo.listRecords","repo.createRecord","repo.deleteRecord","repo.uploadBlob","repo.applyWrites","repo.putRecord","repo.importRepo","repo.listMissingBlobs","server.describeServer","server.createSession","server.refreshSession","server.getSession","server.deleteSession","server.getAccountStatus","server.activateAccount","server.deactivateAccount","server.resetMigration","server.getServiceAuth"],"sources":["../src/storage.ts","../src/oauth-storage.ts","../src/sequencer.ts","../src/blobs.ts","../src/account-do.ts","../src/service-auth.ts","../src/session.ts","../src/oauth.ts","../src/middleware/auth.ts","../src/did-resolver.ts","../src/did-cache.ts","../src/xrpc-proxy.ts","../src/xrpc/sync.ts","../src/lexicons/app.bsky.actor.defs.json","../src/lexicons/app.bsky.actor.profile.json","../src/lexicons/app.bsky.embed.defs.json","../src/lexicons/app.bsky.embed.external.json","../src/lexicons/app.bsky.embed.images.json","../src/lexicons/app.bsky.embed.record.json","../src/lexicons/app.bsky.embed.recordWithMedia.json","../src/lexicons/app.bsky.embed.video.json","../src/lexicons/app.bsky.feed.defs.json","../src/lexicons/app.bsky.feed.like.json","../src/lexicons/app.bsky.feed.post.json","../src/lexicons/app.bsky.feed.repost.json","../src/lexicons/app.bsky.feed.threadgate.json","../src/lexicons/app.bsky.graph.block.json","../src/lexicons/app.bsky.graph.defs.json","../src/lexicons/app.bsky.graph.follow.json","../src/lexicons/app.bsky.graph.list.json","../src/lexicons/app.bsky.graph.listitem.json","../src/lexicons/app.bsky.notification.defs.json","../src/lexicons/app.bsky.richtext.facet.json","../src/lexicons/com.atproto.label.defs.json","../src/lexicons/com.atproto.repo.strongRef.json","../src/validation.ts","../src/xrpc/repo.ts","../src/xrpc/server.ts","../package.json","../src/index.ts"],"sourcesContent":["import { CID } from \"@atproto/lex-data\";\nimport { BlockMap, type CommitData } from \"@atproto/repo\";\nimport { ReadableBlockstore, type RepoStorage } from \"@atproto/repo\";\n\n/**\n * SQLite-backed repository storage for Cloudflare Durable Objects.\n *\n * Implements the RepoStorage interface from @atproto/repo, storing blocks\n * in a SQLite database within a Durable Object.\n */\nexport class SqliteRepoStorage\n\textends ReadableBlockstore\n\timplements RepoStorage\n{\n\tconstructor(private sql: SqlStorage) {\n\t\tsuper();\n\t}\n\n\t/**\n\t * Initialize the database schema. Should be called once on DO startup.\n\t * @param initialActive - Whether the account should start in active state (default true)\n\t */\n\tinitSchema(initialActive: boolean = true): void {\n\t\tthis.sql.exec(`\n\t\t\t-- Block storage (MST nodes + record blocks)\n\t\t\tCREATE TABLE IF NOT EXISTS blocks (\n\t\t\t\tcid TEXT PRIMARY KEY,\n\t\t\t\tbytes BLOB NOT NULL,\n\t\t\t\trev TEXT NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_blocks_rev ON blocks(rev);\n\n\t\t\t-- Repo state (single row)\n\t\t\tCREATE TABLE IF NOT EXISTS repo_state (\n\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\troot_cid TEXT,\n\t\t\t\trev TEXT,\n\t\t\t\tseq INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tactive INTEGER NOT NULL DEFAULT 1\n\t\t\t);\n\n\t\t\t-- Initialize with empty state if not exists\n\t\t\tINSERT OR IGNORE INTO repo_state (id, root_cid, rev, seq, active)\n\t\t\tVALUES (1, NULL, NULL, 0, ${initialActive ? 1 : 0});\n\n\t\t\t-- Firehose events (sequenced commit log)\n\t\t\tCREATE TABLE IF NOT EXISTS firehose_events (\n\t\t\t\tseq INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tevent_type TEXT NOT NULL,\n\t\t\t\tpayload BLOB NOT NULL,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_firehose_created_at ON firehose_events(created_at);\n\n\t\t\t-- User preferences (single row, stores JSON array)\n\t\t\tCREATE TABLE IF NOT EXISTS preferences (\n\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\tdata TEXT NOT NULL DEFAULT '[]'\n\t\t\t);\n\n\t\t\t-- Initialize with empty preferences array if not exists\n\t\t\tINSERT OR IGNORE INTO preferences (id, data) VALUES (1, '[]');\n\n\t\t\t-- Track blob references in records (populated during importRepo)\n\t\t\tCREATE TABLE IF NOT EXISTS record_blob (\n\t\t\t\trecordUri TEXT NOT NULL,\n\t\t\t\tblobCid TEXT NOT NULL,\n\t\t\t\tPRIMARY KEY (recordUri, blobCid)\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_record_blob_cid ON record_blob(blobCid);\n\n\t\t\t-- Track successfully imported blobs (populated during uploadBlob)\n\t\t\tCREATE TABLE IF NOT EXISTS imported_blobs (\n\t\t\t\tcid TEXT PRIMARY KEY,\n\t\t\t\tsize INTEGER NOT NULL,\n\t\t\t\tmimeType TEXT,\n\t\t\t\tcreatedAt TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t);\n\t\t`);\n\t}\n\n\t/**\n\t * Get the current root CID of the repository.\n\t */\n\tasync getRoot(): Promise<CID | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT root_cid FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\tif (rows.length === 0 || !rows[0]?.root_cid) {\n\t\t\treturn null;\n\t\t}\n\t\treturn CID.parse(rows[0]!.root_cid as string);\n\t}\n\n\t/**\n\t * Get the current revision string.\n\t */\n\tasync getRev(): Promise<string | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT rev FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.rev as string) ?? null) : null;\n\t}\n\n\t/**\n\t * Get the current sequence number for firehose events.\n\t */\n\tasync getSeq(): Promise<number> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT seq FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.seq as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Increment and return the next sequence number.\n\t */\n\tasync nextSeq(): Promise<number> {\n\t\tthis.sql.exec(\"UPDATE repo_state SET seq = seq + 1 WHERE id = 1\");\n\t\treturn this.getSeq();\n\t}\n\n\t/**\n\t * Get the raw bytes for a block by CID.\n\t */\n\tasync getBytes(cid: CID): Promise<Uint8Array | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT bytes FROM blocks WHERE cid = ?\", cid.toString())\n\t\t\t.toArray();\n\t\tif (rows.length === 0 || !rows[0]?.bytes) {\n\t\t\treturn null;\n\t\t}\n\t\t// SQLite returns ArrayBuffer, convert to Uint8Array\n\t\treturn new Uint8Array(rows[0]!.bytes as ArrayBuffer);\n\t}\n\n\t/**\n\t * Check if a block exists.\n\t */\n\tasync has(cid: CID): Promise<boolean> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT 1 FROM blocks WHERE cid = ? LIMIT 1\", cid.toString())\n\t\t\t.toArray();\n\t\treturn rows.length > 0;\n\t}\n\n\t/**\n\t * Get multiple blocks at once.\n\t */\n\tasync getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }> {\n\t\tconst blocks = new BlockMap();\n\t\tconst missing: CID[] = [];\n\n\t\tfor (const cid of cids) {\n\t\t\tconst bytes = await this.getBytes(cid);\n\t\t\tif (bytes) {\n\t\t\t\tblocks.set(cid, bytes);\n\t\t\t} else {\n\t\t\t\tmissing.push(cid);\n\t\t\t}\n\t\t}\n\n\t\treturn { blocks, missing };\n\t}\n\n\t/**\n\t * Store a single block.\n\t */\n\tasync putBlock(cid: CID, block: Uint8Array, rev: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR REPLACE INTO blocks (cid, bytes, rev) VALUES (?, ?, ?)\",\n\t\t\tcid.toString(),\n\t\t\tblock,\n\t\t\trev,\n\t\t);\n\t}\n\n\t/**\n\t * Store multiple blocks at once.\n\t */\n\tasync putMany(blocks: BlockMap, rev: string): Promise<void> {\n\t\t// Access BlockMap's internal map to avoid iterator issues in Workers environment\n\t\t// BlockMap stores data in a Map<string, Uint8Array> internally as 'map' (private field)\n\t\tconst internalMap = (blocks as unknown as { map: Map<string, Uint8Array> })\n\t\t\t.map;\n\t\tif (internalMap) {\n\t\t\tfor (const [cidStr, bytes] of internalMap) {\n\t\t\t\tthis.sql.exec(\n\t\t\t\t\t\"INSERT OR REPLACE INTO blocks (cid, bytes, rev) VALUES (?, ?, ?)\",\n\t\t\t\t\tcidStr,\n\t\t\t\t\tbytes,\n\t\t\t\t\trev,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update the repository root.\n\t */\n\tasync updateRoot(cid: CID, rev: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE repo_state SET root_cid = ?, rev = ? WHERE id = 1\",\n\t\t\tcid.toString(),\n\t\t\trev,\n\t\t);\n\t}\n\n\t/**\n\t * Apply a commit atomically: add new blocks, remove old blocks, update root.\n\t */\n\tasync applyCommit(commit: CommitData): Promise<void> {\n\t\t// Note: Durable Object SQLite doesn't support BEGIN/COMMIT,\n\t\t// but operations within a single DO request are already atomic.\n\n\t\t// Access BlockMap's internal map to avoid iterator issues in Workers environment\n\t\tconst internalMap = (\n\t\t\tcommit.newBlocks as unknown as { map: Map<string, Uint8Array> }\n\t\t).map;\n\t\tif (internalMap) {\n\t\t\tfor (const [cidStr, bytes] of internalMap) {\n\t\t\t\tthis.sql.exec(\n\t\t\t\t\t\"INSERT OR REPLACE INTO blocks (cid, bytes, rev) VALUES (?, ?, ?)\",\n\t\t\t\t\tcidStr,\n\t\t\t\t\tbytes,\n\t\t\t\t\tcommit.rev,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Remove old blocks - access CidSet's internal set to avoid CID.parse shim issues\n\t\tconst removedSet = (commit.removedCids as unknown as { set: Set<string> })\n\t\t\t.set;\n\t\tif (removedSet) {\n\t\t\tfor (const cidStr of removedSet) {\n\t\t\t\tthis.sql.exec(\"DELETE FROM blocks WHERE cid = ?\", cidStr);\n\t\t\t}\n\t\t}\n\n\t\t// Update root\n\t\tawait this.updateRoot(commit.cid, commit.rev);\n\t}\n\n\t/**\n\t * Get total storage size in bytes.\n\t */\n\tasync sizeInBytes(): Promise<number> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT SUM(LENGTH(bytes)) as total FROM blocks\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.total as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Clear all data (for testing).\n\t */\n\tasync destroy(): Promise<void> {\n\t\tthis.sql.exec(\"DELETE FROM blocks\");\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE repo_state SET root_cid = NULL, rev = NULL WHERE id = 1\",\n\t\t);\n\t}\n\n\t/**\n\t * Count the number of blocks stored.\n\t */\n\tasync countBlocks(): Promise<number> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT COUNT(*) as count FROM blocks\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.count as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Get user preferences.\n\t */\n\tasync getPreferences(): Promise<unknown[]> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT data FROM preferences WHERE id = 1\")\n\t\t\t.toArray();\n\t\tif (rows.length === 0 || !rows[0]?.data) {\n\t\t\treturn [];\n\t\t}\n\t\tconst data = rows[0]!.data as string;\n\t\ttry {\n\t\t\treturn JSON.parse(data);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Update user preferences.\n\t */\n\tasync putPreferences(preferences: unknown[]): Promise<void> {\n\t\tconst data = JSON.stringify(preferences);\n\t\tthis.sql.exec(\"UPDATE preferences SET data = ? WHERE id = 1\", data);\n\t}\n\n\t/**\n\t * Get the activation state of the account.\n\t */\n\tasync getActive(): Promise<boolean> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT active FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.active as number) === 1) : true;\n\t}\n\n\t/**\n\t * Set the activation state of the account.\n\t */\n\tasync setActive(active: boolean): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE repo_state SET active = ? WHERE id = 1\",\n\t\t\tactive ? 1 : 0,\n\t\t);\n\t}\n\n\t// ============================================\n\t// Blob Tracking Methods\n\t// ============================================\n\n\t/**\n\t * Add a blob reference from a record.\n\t */\n\taddRecordBlob(recordUri: string, blobCid: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR IGNORE INTO record_blob (recordUri, blobCid) VALUES (?, ?)\",\n\t\t\trecordUri,\n\t\t\tblobCid,\n\t\t);\n\t}\n\n\t/**\n\t * Add multiple blob references from a record.\n\t */\n\taddRecordBlobs(recordUri: string, blobCids: string[]): void {\n\t\tfor (const cid of blobCids) {\n\t\t\tthis.addRecordBlob(recordUri, cid);\n\t\t}\n\t}\n\n\t/**\n\t * Remove all blob references for a record.\n\t */\n\tremoveRecordBlobs(recordUri: string): void {\n\t\tthis.sql.exec(\"DELETE FROM record_blob WHERE recordUri = ?\", recordUri);\n\t}\n\n\t/**\n\t * Track an imported blob.\n\t */\n\ttrackImportedBlob(cid: string, size: number, mimeType: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR REPLACE INTO imported_blobs (cid, size, mimeType) VALUES (?, ?, ?)\",\n\t\t\tcid,\n\t\t\tsize,\n\t\t\tmimeType,\n\t\t);\n\t}\n\n\t/**\n\t * Check if a blob has been imported.\n\t */\n\tisBlobImported(cid: string): boolean {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT 1 FROM imported_blobs WHERE cid = ? LIMIT 1\", cid)\n\t\t\t.toArray();\n\t\treturn rows.length > 0;\n\t}\n\n\t/**\n\t * Count expected blobs (distinct blobs referenced by records).\n\t */\n\tcountExpectedBlobs(): number {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT COUNT(DISTINCT blobCid) as count FROM record_blob\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.count as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Count imported blobs.\n\t */\n\tcountImportedBlobs(): number {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT COUNT(*) as count FROM imported_blobs\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.count as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * List blobs that are referenced but not yet imported.\n\t */\n\tlistMissingBlobs(\n\t\tlimit: number = 500,\n\t\tcursor?: string,\n\t): { blobs: Array<{ cid: string; recordUri: string }>; cursor?: string } {\n\t\tconst blobs: Array<{ cid: string; recordUri: string }> = [];\n\n\t\t// Get blobs referenced but not in imported_blobs\n\t\tconst query = cursor\n\t\t\t? `SELECT rb.blobCid, rb.recordUri FROM record_blob rb\n\t\t\t\t LEFT JOIN imported_blobs ib ON rb.blobCid = ib.cid\n\t\t\t\t WHERE ib.cid IS NULL AND rb.blobCid > ?\n\t\t\t\t ORDER BY rb.blobCid\n\t\t\t\t LIMIT ?`\n\t\t\t: `SELECT rb.blobCid, rb.recordUri FROM record_blob rb\n\t\t\t\t LEFT JOIN imported_blobs ib ON rb.blobCid = ib.cid\n\t\t\t\t WHERE ib.cid IS NULL\n\t\t\t\t ORDER BY rb.blobCid\n\t\t\t\t LIMIT ?`;\n\n\t\tconst rows = cursor\n\t\t\t? this.sql.exec(query, cursor, limit + 1).toArray()\n\t\t\t: this.sql.exec(query, limit + 1).toArray();\n\n\t\tfor (const row of rows.slice(0, limit)) {\n\t\t\tblobs.push({\n\t\t\t\tcid: row.blobCid as string,\n\t\t\t\trecordUri: row.recordUri as string,\n\t\t\t});\n\t\t}\n\n\t\tconst hasMore = rows.length > limit;\n\t\tconst nextCursor = hasMore ? blobs[blobs.length - 1]?.cid : undefined;\n\n\t\treturn { blobs, cursor: nextCursor };\n\t}\n\n\t/**\n\t * Clear all blob tracking data (for testing).\n\t */\n\tclearBlobTracking(): void {\n\t\tthis.sql.exec(\"DELETE FROM record_blob\");\n\t\tthis.sql.exec(\"DELETE FROM imported_blobs\");\n\t}\n}\n","import type {\n\tAuthCodeData,\n\tClientMetadata,\n\tOAuthStorage,\n\tPARData,\n\tTokenData,\n} from \"@getcirrus/oauth-provider\";\n\n/**\n * SQLite-backed OAuth storage for Cloudflare Durable Objects.\n *\n * Implements the OAuthStorage interface from @getcirrus/oauth-provider,\n * storing OAuth data in SQLite tables within a Durable Object.\n */\nexport class SqliteOAuthStorage implements OAuthStorage {\n\tconstructor(private sql: SqlStorage) {}\n\n\t/**\n\t * Initialize the OAuth database schema. Should be called once on DO startup.\n\t */\n\tinitSchema(): void {\n\t\tthis.sql.exec(`\n\t\t\t-- Authorization codes (5 min TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_auth_codes (\n\t\t\t\tcode TEXT PRIMARY KEY,\n\t\t\t\tclient_id TEXT NOT NULL,\n\t\t\t\tredirect_uri TEXT NOT NULL,\n\t\t\t\tcode_challenge TEXT NOT NULL,\n\t\t\t\tcode_challenge_method TEXT NOT NULL DEFAULT 'S256',\n\t\t\t\tscope TEXT NOT NULL,\n\t\t\t\tsub TEXT NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_auth_codes_expires ON oauth_auth_codes(expires_at);\n\n\t\t\t-- OAuth tokens\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_tokens (\n\t\t\t\taccess_token TEXT PRIMARY KEY,\n\t\t\t\trefresh_token TEXT NOT NULL UNIQUE,\n\t\t\t\tclient_id TEXT NOT NULL,\n\t\t\t\tsub TEXT NOT NULL,\n\t\t\t\tscope TEXT NOT NULL,\n\t\t\t\tdpop_jkt TEXT,\n\t\t\t\tissued_at INTEGER NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL,\n\t\t\t\trevoked INTEGER NOT NULL DEFAULT 0\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_tokens_refresh ON oauth_tokens(refresh_token);\n\t\t\tCREATE INDEX IF NOT EXISTS idx_tokens_sub ON oauth_tokens(sub);\n\t\t\tCREATE INDEX IF NOT EXISTS idx_tokens_expires ON oauth_tokens(expires_at);\n\n\t\t\t-- Cached client metadata\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_clients (\n\t\t\t\tclient_id TEXT PRIMARY KEY,\n\t\t\t\tclient_name TEXT NOT NULL,\n\t\t\t\tredirect_uris TEXT NOT NULL,\n\t\t\t\tlogo_uri TEXT,\n\t\t\t\tclient_uri TEXT,\n\t\t\t\tcached_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\t-- PAR requests (90 sec TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_par_requests (\n\t\t\t\trequest_uri TEXT PRIMARY KEY,\n\t\t\t\tclient_id TEXT NOT NULL,\n\t\t\t\tparams TEXT NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_par_expires ON oauth_par_requests(expires_at);\n\n\t\t\t-- DPoP nonces for replay prevention (5 min TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_nonces (\n\t\t\t\tnonce TEXT PRIMARY KEY,\n\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_nonces_created ON oauth_nonces(created_at);\n\t\t`);\n\t}\n\n\t/**\n\t * Clean up expired entries. Should be called periodically.\n\t */\n\tcleanup(): void {\n\t\tconst now = Date.now();\n\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes WHERE expires_at < ?\", now);\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_tokens WHERE expires_at < ? AND revoked = 0\",\n\t\t\tnow,\n\t\t);\n\t\tthis.sql.exec(\"DELETE FROM oauth_par_requests WHERE expires_at < ?\", now);\n\t\t// Nonces expire after 5 minutes\n\t\tconst nonceExpiry = now - 5 * 60 * 1000;\n\t\tthis.sql.exec(\"DELETE FROM oauth_nonces WHERE created_at < ?\", nonceExpiry);\n\t}\n\n\t// ============================================\n\t// Authorization Codes\n\t// ============================================\n\n\tasync saveAuthCode(code: string, data: AuthCodeData): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO oauth_auth_codes\n\t\t\t(code, client_id, redirect_uri, code_challenge, code_challenge_method, scope, sub, expires_at)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\tcode,\n\t\t\tdata.clientId,\n\t\t\tdata.redirectUri,\n\t\t\tdata.codeChallenge,\n\t\t\tdata.codeChallengeMethod,\n\t\t\tdata.scope,\n\t\t\tdata.sub,\n\t\t\tdata.expiresAt,\n\t\t);\n\t}\n\n\tasync getAuthCode(code: string): Promise<AuthCodeData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT client_id, redirect_uri, code_challenge, code_challenge_method, scope, sub, expires_at\n\t\t\t\tFROM oauth_auth_codes WHERE code = ?`,\n\t\t\t\tcode,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst expiresAt = row.expires_at as number;\n\n\t\tif (Date.now() > expiresAt) {\n\t\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes WHERE code = ?\", code);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tclientId: row.client_id as string,\n\t\t\tredirectUri: row.redirect_uri as string,\n\t\t\tcodeChallenge: row.code_challenge as string,\n\t\t\tcodeChallengeMethod: row.code_challenge_method as \"S256\",\n\t\t\tscope: row.scope as string,\n\t\t\tsub: row.sub as string,\n\t\t\texpiresAt,\n\t\t};\n\t}\n\n\tasync deleteAuthCode(code: string): Promise<void> {\n\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes WHERE code = ?\", code);\n\t}\n\n\t// ============================================\n\t// Tokens\n\t// ============================================\n\n\tasync saveTokens(data: TokenData): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO oauth_tokens\n\t\t\t(access_token, refresh_token, client_id, sub, scope, dpop_jkt, issued_at, expires_at, revoked)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\tdata.accessToken,\n\t\t\tdata.refreshToken,\n\t\t\tdata.clientId,\n\t\t\tdata.sub,\n\t\t\tdata.scope,\n\t\t\tdata.dpopJkt ?? null,\n\t\t\tdata.issuedAt,\n\t\t\tdata.expiresAt,\n\t\t\tdata.revoked ? 1 : 0,\n\t\t);\n\t}\n\n\tasync getTokenByAccess(accessToken: string): Promise<TokenData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT access_token, refresh_token, client_id, sub, scope, dpop_jkt, issued_at, expires_at, revoked\n\t\t\t\tFROM oauth_tokens WHERE access_token = ?`,\n\t\t\t\taccessToken,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst revoked = Boolean(row.revoked);\n\t\tconst expiresAt = row.expires_at as number;\n\n\t\tif (revoked || Date.now() > expiresAt) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: row.access_token as string,\n\t\t\trefreshToken: row.refresh_token as string,\n\t\t\tclientId: row.client_id as string,\n\t\t\tsub: row.sub as string,\n\t\t\tscope: row.scope as string,\n\t\t\tdpopJkt: (row.dpop_jkt as string) ?? undefined,\n\t\t\tissuedAt: row.issued_at as number,\n\t\t\texpiresAt,\n\t\t\trevoked,\n\t\t};\n\t}\n\n\tasync getTokenByRefresh(refreshToken: string): Promise<TokenData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT access_token, refresh_token, client_id, sub, scope, dpop_jkt, issued_at, expires_at, revoked\n\t\t\t\tFROM oauth_tokens WHERE refresh_token = ?`,\n\t\t\t\trefreshToken,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst revoked = Boolean(row.revoked);\n\n\t\tif (revoked) return null;\n\n\t\treturn {\n\t\t\taccessToken: row.access_token as string,\n\t\t\trefreshToken: row.refresh_token as string,\n\t\t\tclientId: row.client_id as string,\n\t\t\tsub: row.sub as string,\n\t\t\tscope: row.scope as string,\n\t\t\tdpopJkt: (row.dpop_jkt as string) ?? undefined,\n\t\t\tissuedAt: row.issued_at as number,\n\t\t\texpiresAt: row.expires_at as number,\n\t\t\trevoked,\n\t\t};\n\t}\n\n\tasync revokeToken(accessToken: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE oauth_tokens SET revoked = 1 WHERE access_token = ?\",\n\t\t\taccessToken,\n\t\t);\n\t}\n\n\tasync revokeAllTokens(sub: string): Promise<void> {\n\t\tthis.sql.exec(\"UPDATE oauth_tokens SET revoked = 1 WHERE sub = ?\", sub);\n\t}\n\n\t// ============================================\n\t// Clients\n\t// ============================================\n\n\tasync saveClient(clientId: string, metadata: ClientMetadata): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT OR REPLACE INTO oauth_clients\n\t\t\t(client_id, client_name, redirect_uris, logo_uri, client_uri, cached_at)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?)`,\n\t\t\tclientId,\n\t\t\tmetadata.clientName,\n\t\t\tJSON.stringify(metadata.redirectUris),\n\t\t\tmetadata.logoUri ?? null,\n\t\t\tmetadata.clientUri ?? null,\n\t\t\tmetadata.cachedAt ?? Date.now(),\n\t\t);\n\t}\n\n\tasync getClient(clientId: string): Promise<ClientMetadata | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT client_id, client_name, redirect_uris, logo_uri, client_uri, cached_at\n\t\t\t\tFROM oauth_clients WHERE client_id = ?`,\n\t\t\t\tclientId,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\treturn {\n\t\t\tclientId: row.client_id as string,\n\t\t\tclientName: row.client_name as string,\n\t\t\tredirectUris: JSON.parse(row.redirect_uris as string) as string[],\n\t\t\tlogoUri: (row.logo_uri as string) ?? undefined,\n\t\t\tclientUri: (row.client_uri as string) ?? undefined,\n\t\t\tcachedAt: row.cached_at as number,\n\t\t};\n\t}\n\n\t// ============================================\n\t// PAR Requests\n\t// ============================================\n\n\tasync savePAR(requestUri: string, data: PARData): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO oauth_par_requests (request_uri, client_id, params, expires_at)\n\t\t\tVALUES (?, ?, ?, ?)`,\n\t\t\trequestUri,\n\t\t\tdata.clientId,\n\t\t\tJSON.stringify(data.params),\n\t\t\tdata.expiresAt,\n\t\t);\n\t}\n\n\tasync getPAR(requestUri: string): Promise<PARData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT client_id, params, expires_at FROM oauth_par_requests WHERE request_uri = ?`,\n\t\t\t\trequestUri,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst expiresAt = row.expires_at as number;\n\n\t\tif (Date.now() > expiresAt) {\n\t\t\tthis.sql.exec(\n\t\t\t\t\"DELETE FROM oauth_par_requests WHERE request_uri = ?\",\n\t\t\t\trequestUri,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tclientId: row.client_id as string,\n\t\t\tparams: JSON.parse(row.params as string) as Record<string, string>,\n\t\t\texpiresAt,\n\t\t};\n\t}\n\n\tasync deletePAR(requestUri: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_par_requests WHERE request_uri = ?\",\n\t\t\trequestUri,\n\t\t);\n\t}\n\n\t// ============================================\n\t// DPoP Nonces\n\t// ============================================\n\n\tasync checkAndSaveNonce(nonce: string): Promise<boolean> {\n\t\t// Check if nonce already exists\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT 1 FROM oauth_nonces WHERE nonce = ? LIMIT 1\", nonce)\n\t\t\t.toArray();\n\n\t\tif (rows.length > 0) {\n\t\t\treturn false; // Nonce already used\n\t\t}\n\n\t\t// Save the nonce\n\t\tthis.sql.exec(\n\t\t\t\"INSERT INTO oauth_nonces (nonce, created_at) VALUES (?, ?)\",\n\t\t\tnonce,\n\t\t\tDate.now(),\n\t\t);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Clear all OAuth data (for testing).\n\t */\n\tdestroy(): void {\n\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_tokens\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_clients\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_par_requests\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_nonces\");\n\t}\n}\n","import {\n\tencode as cborEncode,\n\tdecode as cborDecode,\n\ttype LexValue,\n} from \"@atproto/lex-cbor\";\nimport { CID } from \"@atproto/lex-data\";\nimport { blocksToCarFile, type BlockMap } from \"@atproto/repo\";\nimport type { RecordWriteOp } from \"@atproto/repo\";\n\n/**\n * Commit event payload for the firehose\n */\nexport interface CommitEvent {\n\tseq: number;\n\trebase: boolean;\n\ttooBig: boolean;\n\trepo: string;\n\tcommit: CID;\n\trev: string;\n\tsince: string | null;\n\tblocks: Uint8Array;\n\tops: RepoOp[];\n\tblobs: CID[];\n\ttime: string;\n}\n\n/**\n * Repository operation in a commit\n */\nexport interface RepoOp {\n\taction: \"create\" | \"update\" | \"delete\";\n\tpath: string;\n\tcid: CID | null;\n}\n\n/**\n * Sequenced event wrapper\n */\nexport interface SeqEvent {\n\tseq: number;\n\ttype: \"commit\";\n\tevent: CommitEvent;\n\ttime: string;\n}\n\n/**\n * Data needed to sequence a commit\n */\nexport interface CommitData {\n\tdid: string;\n\tcommit: CID;\n\trev: string;\n\tsince: string | null;\n\tnewBlocks: BlockMap;\n\tops: Array<RecordWriteOp & { cid?: CID | null }>;\n}\n\n/**\n * Sequencer manages the firehose event log.\n *\n * Stores commit events in SQLite and provides methods for:\n * - Sequencing new commits\n * - Backfilling events from a cursor\n * - Getting the latest sequence number\n */\nexport class Sequencer {\n\tconstructor(private sql: SqlStorage) {}\n\n\t/**\n\t * Add a commit to the firehose sequence.\n\t * Returns the complete sequenced event for broadcasting.\n\t */\n\tasync sequenceCommit(data: CommitData): Promise<SeqEvent> {\n\t\t// Create CAR slice with commit diff\n\t\tconst carBytes = await blocksToCarFile(data.commit, data.newBlocks);\n\t\tconst time = new Date().toISOString();\n\n\t\t// Build event payload\n\t\tconst eventPayload: Omit<CommitEvent, \"seq\"> = {\n\t\t\trepo: data.did,\n\t\t\tcommit: data.commit,\n\t\t\trev: data.rev,\n\t\t\tsince: data.since,\n\t\t\tblocks: carBytes,\n\t\t\tops: data.ops.map(\n\t\t\t\t(op): RepoOp => ({\n\t\t\t\t\taction: op.action as \"create\" | \"update\" | \"delete\",\n\t\t\t\t\tpath: `${op.collection}/${op.rkey}`,\n\t\t\t\t\tcid: (\"cid\" in op && op.cid ? op.cid : null) as CID | null,\n\t\t\t\t}),\n\t\t\t),\n\t\t\trebase: false,\n\t\t\ttooBig: carBytes.length > 1_000_000,\n\t\t\tblobs: [],\n\t\t\ttime,\n\t\t};\n\n\t\t// Store in SQLite\n\t\t// Type assertion: CBOR handles CID/Uint8Array serialization\n\t\tconst payload = cborEncode(eventPayload as {} as LexValue);\n\t\tconst result = this.sql\n\t\t\t.exec(\n\t\t\t\t`INSERT INTO firehose_events (event_type, payload)\n VALUES ('commit', ?)\n RETURNING seq`,\n\t\t\t\tpayload,\n\t\t\t)\n\t\t\t.one();\n\n\t\tconst seq = result.seq as number;\n\n\t\treturn {\n\t\t\tseq,\n\t\t\ttype: \"commit\",\n\t\t\tevent: {\n\t\t\t\t...eventPayload,\n\t\t\t\tseq,\n\t\t\t},\n\t\t\ttime,\n\t\t};\n\t}\n\n\t/**\n\t * Get events from a cursor position.\n\t * Returns up to `limit` events after the cursor.\n\t */\n\tasync getEventsSince(cursor: number, limit = 100): Promise<SeqEvent[]> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT seq, event_type, payload, created_at\n FROM firehose_events\n WHERE seq > ?\n ORDER BY seq ASC\n LIMIT ?`,\n\t\t\t\tcursor,\n\t\t\t\tlimit,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\treturn rows.map((row) => {\n\t\t\tconst payload = new Uint8Array(row.payload as ArrayBuffer);\n\t\t\tconst decoded = cborDecode(payload);\n\n\t\t\treturn {\n\t\t\t\tseq: row.seq as number,\n\t\t\t\ttype: \"commit\",\n\t\t\t\tevent: {\n\t\t\t\t\t...(decoded as unknown as Omit<CommitEvent, \"seq\">),\n\t\t\t\t\tseq: row.seq as number,\n\t\t\t\t},\n\t\t\t\ttime: row.created_at as string,\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * Get the latest sequence number.\n\t * Returns 0 if no events have been sequenced yet.\n\t */\n\tgetLatestSeq(): number {\n\t\tconst result = this.sql\n\t\t\t.exec(\"SELECT MAX(seq) as seq FROM firehose_events\")\n\t\t\t.one();\n\t\treturn (result?.seq as number) ?? 0;\n\t}\n\n\t/**\n\t * Prune old events to keep the log from growing indefinitely.\n\t * Keeps the most recent `keepCount` events.\n\t */\n\tasync pruneOldEvents(keepCount = 10000): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`DELETE FROM firehose_events\n WHERE seq < (SELECT MAX(seq) - ? FROM firehose_events)`,\n\t\t\tkeepCount,\n\t\t);\n\t}\n}\n","import { CID } from \"@atproto/lex-data\";\nimport { cidForRawBytes } from \"@atproto/lex-cbor\";\n\nexport interface BlobRef {\n\t$type: \"blob\";\n\tref: { $link: string };\n\tmimeType: string;\n\tsize: number;\n}\n\n/**\n * BlobStore manages blob storage in R2.\n * Blobs are stored with CID-based keys prefixed by the account's DID.\n */\nexport class BlobStore {\n\tconstructor(\n\t\tprivate r2: R2Bucket,\n\t\tprivate did: string,\n\t) {}\n\n\t/**\n\t * Upload a blob to R2 and return a BlobRef.\n\t */\n\tasync putBlob(bytes: Uint8Array, mimeType: string): Promise<BlobRef> {\n\t\t// Compute CID using SHA-256 (RAW codec)\n\t\tconst cid = await cidForRawBytes(bytes);\n\n\t\t// Store in R2 with DID prefix for isolation\n\t\tconst key = `${this.did}/${cid.toString()}`;\n\t\tawait this.r2.put(key, bytes, {\n\t\t\thttpMetadata: { contentType: mimeType },\n\t\t});\n\n\t\treturn {\n\t\t\t$type: \"blob\",\n\t\t\tref: { $link: cid.toString() },\n\t\t\tmimeType,\n\t\t\tsize: bytes.length,\n\t\t};\n\t}\n\n\t/**\n\t * Retrieve a blob from R2 by CID.\n\t */\n\tasync getBlob(cid: CID): Promise<R2ObjectBody | null> {\n\t\tconst key = `${this.did}/${cid.toString()}`;\n\t\treturn this.r2.get(key);\n\t}\n\n\t/**\n\t * Check if a blob exists in R2.\n\t */\n\tasync hasBlob(cid: CID): Promise<boolean> {\n\t\tconst key = `${this.did}/${cid.toString()}`;\n\t\tconst head = await this.r2.head(key);\n\t\treturn head !== null;\n\t}\n}\n","import { DurableObject } from \"cloudflare:workers\";\nimport {\n\tRepo,\n\tWriteOpAction,\n\tBlockMap,\n\tblocksToCarFile,\n\treadCarWithRoot,\n\ttype RecordCreateOp,\n\ttype RecordUpdateOp,\n\ttype RecordDeleteOp,\n\ttype RecordWriteOp,\n} from \"@atproto/repo\";\nimport type { RepoRecord } from \"@atproto/lexicon\";\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { CID, isCid, asCid, isBlobRef } from \"@atproto/lex-data\";\nimport { TID } from \"@atproto/common-web\";\nimport { AtUri } from \"@atproto/syntax\";\nimport { encode as cborEncode } from \"@atproto/lex-cbor\";\nimport { SqliteRepoStorage } from \"./storage\";\nimport { SqliteOAuthStorage } from \"./oauth-storage\";\nimport { Sequencer, type SeqEvent, type CommitData } from \"./sequencer\";\nimport { BlobStore, type BlobRef } from \"./blobs\";\nimport type { PDSEnv } from \"./types\";\n\n/**\n * Account Durable Object - manages a single user's AT Protocol repository.\n *\n * This DO provides:\n * - SQLite-backed block storage for the repository\n * - AT Protocol Repo instance for repository operations\n * - Firehose WebSocket connections\n * - Sequence number management\n */\nexport class AccountDurableObject extends DurableObject<PDSEnv> {\n\tprivate storage: SqliteRepoStorage | null = null;\n\tprivate oauthStorage: SqliteOAuthStorage | null = null;\n\tprivate repo: Repo | null = null;\n\tprivate keypair: Secp256k1Keypair | null = null;\n\tprivate sequencer: Sequencer | null = null;\n\tprivate blobStore: BlobStore | null = null;\n\tprivate storageInitialized = false;\n\tprivate repoInitialized = false;\n\n\tconstructor(ctx: DurableObjectState, env: PDSEnv) {\n\t\tsuper(ctx, env);\n\n\t\t// Validate required environment variables at startup\n\t\tif (!env.SIGNING_KEY) {\n\t\t\tthrow new Error(\"Missing required environment variable: SIGNING_KEY\");\n\t\t}\n\t\tif (!env.DID) {\n\t\t\tthrow new Error(\"Missing required environment variable: DID\");\n\t\t}\n\n\t\t// Initialize BlobStore if R2 bucket is available\n\t\tif (env.BLOBS) {\n\t\t\tthis.blobStore = new BlobStore(env.BLOBS, env.DID);\n\t\t}\n\t}\n\n\t/**\n\t * Initialize the storage adapter. Called lazily on first storage access.\n\t */\n\tprivate async ensureStorageInitialized(): Promise<void> {\n\t\tif (!this.storageInitialized) {\n\t\t\tawait this.ctx.blockConcurrencyWhile(async () => {\n\t\t\t\tif (this.storageInitialized) return; // Double-check after acquiring lock\n\n\t\t\t\t// Determine initial active state from env var (default true for new accounts)\n\t\t\t\tconst initialActive =\n\t\t\t\t\tthis.env.INITIAL_ACTIVE === undefined ||\n\t\t\t\t\tthis.env.INITIAL_ACTIVE === \"true\" ||\n\t\t\t\t\tthis.env.INITIAL_ACTIVE === \"1\";\n\n\t\t\t\tthis.storage = new SqliteRepoStorage(this.ctx.storage.sql);\n\t\t\t\tthis.storage.initSchema(initialActive);\n\t\t\t\tthis.oauthStorage = new SqliteOAuthStorage(this.ctx.storage.sql);\n\t\t\t\tthis.oauthStorage.initSchema();\n\t\t\t\tthis.sequencer = new Sequencer(this.ctx.storage.sql);\n\t\t\t\tthis.storageInitialized = true;\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Initialize the Repo instance. Called lazily on first repo access.\n\t */\n\tprivate async ensureRepoInitialized(): Promise<void> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\tif (!this.repoInitialized) {\n\t\t\tawait this.ctx.blockConcurrencyWhile(async () => {\n\t\t\t\tif (this.repoInitialized) return; // Double-check after acquiring lock\n\n\t\t\t\t// Load signing key\n\t\t\t\tthis.keypair = await Secp256k1Keypair.import(this.env.SIGNING_KEY);\n\n\t\t\t\t// Load or create repo\n\t\t\t\tconst root = await this.storage!.getRoot();\n\t\t\t\tif (root) {\n\t\t\t\t\tthis.repo = await Repo.load(this.storage!, root);\n\t\t\t\t} else {\n\t\t\t\t\tthis.repo = await Repo.create(\n\t\t\t\t\t\tthis.storage!,\n\t\t\t\t\t\tthis.env.DID,\n\t\t\t\t\t\tthis.keypair,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.repoInitialized = true;\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Get the storage adapter for direct access (used by tests and internal operations).\n\t */\n\tasync getStorage(): Promise<SqliteRepoStorage> {\n\t\tawait this.ensureStorageInitialized();\n\t\treturn this.storage!;\n\t}\n\n\t/**\n\t * Get the OAuth storage adapter for OAuth operations.\n\t */\n\tasync getOAuthStorage(): Promise<SqliteOAuthStorage> {\n\t\tawait this.ensureStorageInitialized();\n\t\treturn this.oauthStorage!;\n\t}\n\n\t/**\n\t * Get the Repo instance for repository operations.\n\t */\n\tasync getRepo(): Promise<Repo> {\n\t\tawait this.ensureRepoInitialized();\n\t\treturn this.repo!;\n\t}\n\n\t/**\n\t * Ensure the account is active. Throws error if deactivated.\n\t */\n\tasync ensureActive(): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tconst isActive = await storage.getActive();\n\t\tif (!isActive) {\n\t\t\tthrow new Error(\n\t\t\t\t\"AccountDeactivated: Account is deactivated. Call activateAccount to enable writes.\",\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the signing keypair for repository operations.\n\t */\n\tasync getKeypair(): Promise<Secp256k1Keypair> {\n\t\tawait this.ensureRepoInitialized();\n\t\treturn this.keypair!;\n\t}\n\n\t/**\n\t * Update the Repo instance after mutations.\n\t */\n\tasync setRepo(repo: Repo): Promise<void> {\n\t\tthis.repo = repo;\n\t}\n\n\t/**\n\t * RPC method: Get repo metadata for describeRepo\n\t */\n\tasync rpcDescribeRepo(): Promise<{\n\t\tdid: string;\n\t\tcollections: string[];\n\t\tcid: string;\n\t}> {\n\t\tconst repo = await this.getRepo();\n\t\tconst collections: string[] = [];\n\t\tconst seenCollections = new Set<string>();\n\n\t\tfor await (const record of repo.walkRecords()) {\n\t\t\tif (!seenCollections.has(record.collection)) {\n\t\t\t\tseenCollections.add(record.collection);\n\t\t\t\tcollections.push(record.collection);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tdid: repo.did,\n\t\t\tcollections,\n\t\t\tcid: repo.cid.toString(),\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Get a single record\n\t */\n\tasync rpcGetRecord(\n\t\tcollection: string,\n\t\trkey: string,\n\t): Promise<{\n\t\tcid: string;\n\t\trecord: Rpc.Serializable<any>;\n\t} | null> {\n\t\tconst repo = await this.getRepo();\n\n\t\t// Get the CID from the MST\n\t\tconst dataKey = `${collection}/${rkey}`;\n\t\tconst recordCid = await repo.data.get(dataKey);\n\t\tif (!recordCid) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst record = await repo.getRecord(collection, rkey);\n\n\t\tif (!record) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tcid: recordCid.toString(),\n\t\t\trecord: serializeRecord(record) as Rpc.Serializable<any>,\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: List records in a collection\n\t */\n\tasync rpcListRecords(\n\t\tcollection: string,\n\t\topts: {\n\t\t\tlimit: number;\n\t\t\tcursor?: string;\n\t\t\treverse?: boolean;\n\t\t},\n\t): Promise<{\n\t\trecords: Array<{ uri: string; cid: string; value: unknown }>;\n\t\tcursor?: string;\n\t}> {\n\t\tconst repo = await this.getRepo();\n\t\tconst records = [];\n\t\tconst startFrom = opts.cursor || `${collection}/`;\n\n\t\tfor await (const record of repo.walkRecords(startFrom)) {\n\t\t\tif (record.collection !== collection) {\n\t\t\t\tif (records.length > 0) break;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trecords.push({\n\t\t\t\turi: AtUri.make(repo.did, record.collection, record.rkey).toString(),\n\t\t\t\tcid: record.cid.toString(),\n\t\t\t\tvalue: serializeRecord(record.record),\n\t\t\t});\n\n\t\t\tif (records.length >= opts.limit + 1) break;\n\t\t}\n\n\t\tif (opts.reverse) {\n\t\t\trecords.reverse();\n\t\t}\n\n\t\tconst hasMore = records.length > opts.limit;\n\t\tconst results = hasMore ? records.slice(0, opts.limit) : records;\n\t\tconst cursor = hasMore\n\t\t\t? `${collection}/${results[results.length - 1]?.uri.split(\"/\").pop() ?? \"\"}`\n\t\t\t: undefined;\n\n\t\treturn { records: results, cursor };\n\t}\n\n\t/**\n\t * RPC method: Create a record\n\t */\n\tasync rpcCreateRecord(\n\t\tcollection: string,\n\t\trkey: string | undefined,\n\t\trecord: unknown,\n\t): Promise<{\n\t\turi: string;\n\t\tcid: string;\n\t\tcommit: { cid: string; rev: string };\n\t}> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\tconst actualRkey = rkey || TID.nextStr();\n\t\tconst createOp: RecordCreateOp = {\n\t\t\taction: WriteOpAction.Create,\n\t\t\tcollection,\n\t\t\trkey: actualRkey,\n\t\t\trecord: record as RepoRecord,\n\t\t};\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites([createOp], keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Get the CID for the created record from the MST\n\t\tconst dataKey = `${collection}/${actualRkey}`;\n\t\tconst recordCid = await this.repo.data.get(dataKey);\n\n\t\tif (!recordCid) {\n\t\t\tthrow new Error(`Failed to create record: ${collection}/${actualRkey}`);\n\t\t}\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\t// Get blocks that changed\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\t// Include the record CID in the op for the firehose\n\t\t\tconst opWithCid = { ...createOp, cid: recordCid };\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: [opWithCid],\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\turi: AtUri.make(this.repo.did, collection, actualRkey).toString(),\n\t\t\tcid: recordCid.toString(),\n\t\t\tcommit: {\n\t\t\t\tcid: this.repo.cid.toString(),\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Delete a record\n\t */\n\tasync rpcDeleteRecord(\n\t\tcollection: string,\n\t\trkey: string,\n\t): Promise<{ commit: { cid: string; rev: string } } | null> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\tconst existing = await repo.getRecord(collection, rkey);\n\t\tif (!existing) return null;\n\n\t\tconst deleteOp: RecordDeleteOp = {\n\t\t\taction: WriteOpAction.Delete,\n\t\t\tcollection,\n\t\t\trkey,\n\t\t};\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites([deleteOp], keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\t// Get blocks that changed\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: [deleteOp],\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\tcommit: {\n\t\t\t\tcid: updatedRepo.cid.toString(),\n\t\t\t\trev: updatedRepo.commit.rev,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Put a record (create or update)\n\t */\n\tasync rpcPutRecord(\n\t\tcollection: string,\n\t\trkey: string,\n\t\trecord: unknown,\n\t): Promise<{\n\t\turi: string;\n\t\tcid: string;\n\t\tcommit: { cid: string; rev: string };\n\t\tvalidationStatus: string;\n\t}> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\t// Check if record exists to determine create vs update\n\t\tconst existing = await repo.getRecord(collection, rkey);\n\t\tconst isUpdate = existing !== null;\n\n\t\tconst op: RecordWriteOp = isUpdate\n\t\t\t? ({\n\t\t\t\t\taction: WriteOpAction.Update,\n\t\t\t\t\tcollection,\n\t\t\t\t\trkey,\n\t\t\t\t\trecord: record as RepoRecord,\n\t\t\t\t} as RecordUpdateOp)\n\t\t\t: ({\n\t\t\t\t\taction: WriteOpAction.Create,\n\t\t\t\t\tcollection,\n\t\t\t\t\trkey,\n\t\t\t\t\trecord: record as RepoRecord,\n\t\t\t\t} as RecordCreateOp);\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites([op], keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Get the CID for the record from the MST\n\t\tconst dataKey = `${collection}/${rkey}`;\n\t\tconst recordCid = await this.repo.data.get(dataKey);\n\n\t\tif (!recordCid) {\n\t\t\tthrow new Error(`Failed to put record: ${collection}/${rkey}`);\n\t\t}\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\tconst opWithCid = { ...op, cid: recordCid };\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: [opWithCid],\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\turi: AtUri.make(this.repo.did, collection, rkey).toString(),\n\t\t\tcid: recordCid.toString(),\n\t\t\tcommit: {\n\t\t\t\tcid: this.repo.cid.toString(),\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t},\n\t\t\tvalidationStatus: \"valid\",\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Apply multiple writes (batch create/update/delete)\n\t */\n\tasync rpcApplyWrites(\n\t\twrites: Array<{\n\t\t\t$type: string;\n\t\t\tcollection: string;\n\t\t\trkey?: string;\n\t\t\tvalue?: unknown;\n\t\t}>,\n\t): Promise<{\n\t\tcommit: { cid: string; rev: string };\n\t\tresults: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t}>;\n\t}> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\t// Convert input writes to RecordWriteOp format\n\t\tconst ops: RecordWriteOp[] = [];\n\t\tconst results: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t\tcollection: string;\n\t\t\trkey: string;\n\t\t\taction: WriteOpAction;\n\t\t}> = [];\n\n\t\tfor (const write of writes) {\n\t\t\tif (write.$type === \"com.atproto.repo.applyWrites#create\") {\n\t\t\t\tconst rkey = write.rkey || TID.nextStr();\n\t\t\t\tconst op: RecordCreateOp = {\n\t\t\t\t\taction: WriteOpAction.Create,\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey,\n\t\t\t\t\trecord: write.value as RepoRecord,\n\t\t\t\t};\n\t\t\t\tops.push(op);\n\t\t\t\tresults.push({\n\t\t\t\t\t$type: \"com.atproto.repo.applyWrites#createResult\",\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey,\n\t\t\t\t\taction: WriteOpAction.Create,\n\t\t\t\t});\n\t\t\t} else if (write.$type === \"com.atproto.repo.applyWrites#update\") {\n\t\t\t\tif (!write.rkey) {\n\t\t\t\t\tthrow new Error(\"Update requires rkey\");\n\t\t\t\t}\n\t\t\t\tconst op: RecordUpdateOp = {\n\t\t\t\t\taction: WriteOpAction.Update,\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t\trecord: write.value as RepoRecord,\n\t\t\t\t};\n\t\t\t\tops.push(op);\n\t\t\t\tresults.push({\n\t\t\t\t\t$type: \"com.atproto.repo.applyWrites#updateResult\",\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t\taction: WriteOpAction.Update,\n\t\t\t\t});\n\t\t\t} else if (write.$type === \"com.atproto.repo.applyWrites#delete\") {\n\t\t\t\tif (!write.rkey) {\n\t\t\t\t\tthrow new Error(\"Delete requires rkey\");\n\t\t\t\t}\n\t\t\t\tconst op: RecordDeleteOp = {\n\t\t\t\t\taction: WriteOpAction.Delete,\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t};\n\t\t\t\tops.push(op);\n\t\t\t\tresults.push({\n\t\t\t\t\t$type: \"com.atproto.repo.applyWrites#deleteResult\",\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t\taction: WriteOpAction.Delete,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error(`Unknown write type: ${write.$type}`);\n\t\t\t}\n\t\t}\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites(ops, keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Build final results with CIDs and prepare ops with CIDs for firehose\n\t\tconst finalResults: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t}> = [];\n\t\tconst opsWithCids: Array<RecordWriteOp & { cid?: CID | null }> = [];\n\n\t\tfor (let i = 0; i < results.length; i++) {\n\t\t\tconst result = results[i]!;\n\t\t\tconst op = ops[i]!;\n\n\t\t\tif (result.action === WriteOpAction.Delete) {\n\t\t\t\tfinalResults.push({\n\t\t\t\t\t$type: result.$type,\n\t\t\t\t});\n\t\t\t\topsWithCids.push(op);\n\t\t\t} else {\n\t\t\t\t// Get the CID for create/update\n\t\t\t\tconst dataKey = `${result.collection}/${result.rkey}`;\n\t\t\t\tconst recordCid = await this.repo.data.get(dataKey);\n\t\t\t\tfinalResults.push({\n\t\t\t\t\t$type: result.$type,\n\t\t\t\t\turi: AtUri.make(\n\t\t\t\t\t\tthis.repo.did,\n\t\t\t\t\t\tresult.collection,\n\t\t\t\t\t\tresult.rkey,\n\t\t\t\t\t).toString(),\n\t\t\t\t\tcid: recordCid?.toString(),\n\t\t\t\t\tvalidationStatus: \"valid\",\n\t\t\t\t});\n\t\t\t\t// Include the record CID in the op for the firehose\n\t\t\t\topsWithCids.push({ ...op, cid: recordCid });\n\t\t\t}\n\t\t}\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: opsWithCids,\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\tcommit: {\n\t\t\t\tcid: this.repo.cid.toString(),\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t},\n\t\t\tresults: finalResults,\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Get repo status\n\t */\n\tasync rpcGetRepoStatus(): Promise<{\n\t\tdid: string;\n\t\thead: string;\n\t\trev: string;\n\t}> {\n\t\tconst repo = await this.getRepo();\n\t\treturn {\n\t\t\tdid: repo.did,\n\t\t\thead: repo.cid.toString(),\n\t\t\trev: repo.commit.rev,\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Export repo as CAR\n\t */\n\tasync rpcGetRepoCar(): Promise<Uint8Array> {\n\t\tconst storage = await this.getStorage();\n\t\tconst root = await storage.getRoot();\n\n\t\tif (!root) {\n\t\t\tthrow new Error(\"No repository root found\");\n\t\t}\n\n\t\t// Get all blocks from SQLite storage\n\t\tconst rows = this.ctx.storage.sql\n\t\t\t.exec(\"SELECT cid, bytes FROM blocks\")\n\t\t\t.toArray();\n\n\t\t// Build BlockMap\n\t\tconst blocks = new BlockMap();\n\t\tfor (const row of rows) {\n\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\tblocks.set(cid, bytes);\n\t\t}\n\n\t\t// Use the official CAR builder\n\t\treturn blocksToCarFile(root, blocks);\n\t}\n\n\t/**\n\t * RPC method: Get specific blocks by CID as CAR file\n\t * Used for partial sync and migration.\n\t */\n\tasync rpcGetBlocks(cids: string[]): Promise<Uint8Array> {\n\t\tconst storage = await this.getStorage();\n\t\tconst root = await storage.getRoot();\n\n\t\tif (!root) {\n\t\t\tthrow new Error(\"No repository root found\");\n\t\t}\n\n\t\t// Get requested blocks\n\t\tconst blocks = new BlockMap();\n\t\tfor (const cidStr of cids) {\n\t\t\tconst cid = CID.parse(cidStr);\n\t\t\tconst bytes = await storage.getBytes(cid);\n\t\t\tif (bytes) {\n\t\t\t\tblocks.set(cid, bytes);\n\t\t\t}\n\t\t}\n\n\t\t// Return CAR file with requested blocks\n\t\treturn blocksToCarFile(root, blocks);\n\t}\n\n\t/**\n\t * RPC method: Import repo from CAR file\n\t * This is used for account migration - importing an existing repository\n\t * from another PDS.\n\t */\n\tasync rpcImportRepo(carBytes: Uint8Array): Promise<{\n\t\tdid: string;\n\t\trev: string;\n\t\tcid: string;\n\t}> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\t// Check if account is active - only allow imports on deactivated accounts\n\t\tconst isActive = await this.storage!.getActive();\n\t\tconst existingRoot = await this.storage!.getRoot();\n\n\t\tif (isActive && existingRoot) {\n\t\t\t// Account is active - reject import to prevent accidental overwrites\n\t\t\tthrow new Error(\n\t\t\t\t\"Repository already exists. Cannot import over existing repository.\",\n\t\t\t);\n\t\t}\n\n\t\t// If deactivated and repo exists, clear it first\n\t\tif (existingRoot) {\n\t\t\tawait this.storage!.destroy();\n\t\t\tthis.repo = null;\n\t\t\tthis.repoInitialized = false;\n\t\t}\n\n\t\t// Use official @atproto/repo utilities to read and validate CAR\n\t\t// readCarWithRoot validates single root requirement and returns BlockMap\n\t\tconst { root: rootCid, blocks } = await readCarWithRoot(carBytes);\n\n\t\t// Import all blocks into storage using putMany (more efficient than individual putBlock)\n\t\tconst importRev = TID.nextStr();\n\t\tawait this.storage!.putMany(blocks, importRev);\n\n\t\t// Load the repo to verify it's valid and get the actual revision\n\t\tthis.keypair = await Secp256k1Keypair.import(this.env.SIGNING_KEY);\n\t\tthis.repo = await Repo.load(this.storage!, rootCid);\n\n\t\t// Persist the root CID in storage so getRoot() works correctly\n\t\tawait this.storage!.updateRoot(rootCid, this.repo.commit.rev);\n\n\t\t// Verify the DID matches to prevent incorrect migrations\n\t\tif (this.repo.did !== this.env.DID) {\n\t\t\t// Clean up imported blocks\n\t\t\tawait this.storage!.destroy();\n\t\t\tthrow new Error(\n\t\t\t\t`DID mismatch: CAR file contains DID ${this.repo.did}, but expected ${this.env.DID}`,\n\t\t\t);\n\t\t}\n\n\t\tthis.repoInitialized = true;\n\n\t\t// Extract blob references from all imported records for tracking\n\t\tfor await (const record of this.repo.walkRecords()) {\n\t\t\tconst blobCids = extractBlobCids(record.record);\n\t\t\tif (blobCids.length > 0) {\n\t\t\t\tconst uri = AtUri.make(\n\t\t\t\t\tthis.repo.did,\n\t\t\t\t\trecord.collection,\n\t\t\t\t\trecord.rkey,\n\t\t\t\t).toString();\n\t\t\t\tthis.storage!.addRecordBlobs(uri, blobCids);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tdid: this.repo.did,\n\t\t\trev: this.repo.commit.rev,\n\t\t\tcid: rootCid.toString(),\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Upload a blob to R2\n\t */\n\tasync rpcUploadBlob(bytes: Uint8Array, mimeType: string): Promise<BlobRef> {\n\t\tif (!this.blobStore) {\n\t\t\tthrow new Error(\"Blob storage not configured\");\n\t\t}\n\n\t\t// Enforce size limit (5MB)\n\t\tconst MAX_BLOB_SIZE = 5 * 1024 * 1024;\n\t\tif (bytes.length > MAX_BLOB_SIZE) {\n\t\t\tthrow new Error(\n\t\t\t\t`Blob too large: ${bytes.length} bytes (max ${MAX_BLOB_SIZE})`,\n\t\t\t);\n\t\t}\n\n\t\tconst blobRef = await this.blobStore.putBlob(bytes, mimeType);\n\n\t\t// Track the imported blob for migration progress\n\t\tconst storage = await this.getStorage();\n\t\tstorage.trackImportedBlob(blobRef.ref.$link, bytes.length, mimeType);\n\n\t\treturn blobRef;\n\t}\n\n\t/**\n\t * RPC method: Get a blob from R2\n\t */\n\tasync rpcGetBlob(cidStr: string): Promise<R2ObjectBody | null> {\n\t\tif (!this.blobStore) {\n\t\t\tthrow new Error(\"Blob storage not configured\");\n\t\t}\n\n\t\tconst cid = CID.parse(cidStr);\n\t\treturn this.blobStore.getBlob(cid);\n\t}\n\n\t/**\n\t * Encode a firehose frame (header + body CBOR).\n\t */\n\tprivate encodeFrame(header: object, body: object): Uint8Array {\n\t\tconst headerBytes = cborEncode(header as any);\n\t\tconst bodyBytes = cborEncode(body as any);\n\n\t\tconst frame = new Uint8Array(headerBytes.length + bodyBytes.length);\n\t\tframe.set(headerBytes, 0);\n\t\tframe.set(bodyBytes, headerBytes.length);\n\n\t\treturn frame;\n\t}\n\n\t/**\n\t * Encode a commit event frame.\n\t */\n\tprivate encodeCommitFrame(event: SeqEvent): Uint8Array {\n\t\tconst header = { op: 1, t: \"#commit\" };\n\t\treturn this.encodeFrame(header, event.event);\n\t}\n\n\t/**\n\t * Encode an error frame.\n\t */\n\tprivate encodeErrorFrame(error: string, message: string): Uint8Array {\n\t\tconst header = { op: -1 };\n\t\tconst body = { error, message };\n\t\treturn this.encodeFrame(header, body);\n\t}\n\n\t/**\n\t * Backfill firehose events from a cursor.\n\t */\n\tprivate async backfillFirehose(ws: WebSocket, cursor: number): Promise<void> {\n\t\tif (!this.sequencer) {\n\t\t\tthrow new Error(\"Sequencer not initialized\");\n\t\t}\n\n\t\tconst latestSeq = this.sequencer.getLatestSeq();\n\n\t\t// Check if cursor is in the future\n\t\tif (cursor > latestSeq) {\n\t\t\tconst frame = this.encodeErrorFrame(\n\t\t\t\t\"FutureCursor\",\n\t\t\t\t\"Cursor is in the future\",\n\t\t\t);\n\t\t\tws.send(frame);\n\t\t\tws.close(1008, \"FutureCursor\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Backfill from cursor\n\t\tconst events = await this.sequencer.getEventsSince(cursor, 1000);\n\n\t\tfor (const event of events) {\n\t\t\tconst frame = this.encodeCommitFrame(event);\n\t\t\tws.send(frame);\n\t\t}\n\n\t\t// Update cursor in attachment\n\t\tif (events.length > 0) {\n\t\t\tconst lastEvent = events[events.length - 1];\n\t\t\tif (lastEvent) {\n\t\t\t\tconst attachment = ws.deserializeAttachment() as { cursor: number };\n\t\t\t\tattachment.cursor = lastEvent.seq;\n\t\t\t\tws.serializeAttachment(attachment);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Broadcast a commit event to all connected firehose clients.\n\t */\n\tprivate async broadcastCommit(event: SeqEvent): Promise<void> {\n\t\tconst frame = this.encodeCommitFrame(event);\n\n\t\tfor (const ws of this.ctx.getWebSockets()) {\n\t\t\ttry {\n\t\t\t\tws.send(frame);\n\n\t\t\t\t// Update cursor\n\t\t\t\tconst attachment = ws.deserializeAttachment() as { cursor: number };\n\t\t\t\tattachment.cursor = event.seq;\n\t\t\t\tws.serializeAttachment(attachment);\n\t\t\t} catch (e) {\n\t\t\t\t// Client disconnected, will be cleaned up\n\t\t\t\tconsole.error(\"Error broadcasting to WebSocket:\", e);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Handle WebSocket upgrade for firehose (subscribeRepos).\n\t */\n\tasync handleFirehoseUpgrade(request: Request): Promise<Response> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\tconst url = new URL(request.url);\n\t\tconst cursorParam = url.searchParams.get(\"cursor\");\n\t\tconst cursor = cursorParam ? parseInt(cursorParam, 10) : null;\n\n\t\t// Create WebSocket pair\n\t\tconst pair = new WebSocketPair();\n\t\tconst client = pair[0];\n\t\tconst server = pair[1];\n\n\t\t// Accept with hibernation\n\t\tthis.ctx.acceptWebSocket(server);\n\n\t\t// Store cursor in attachment\n\t\tserver.serializeAttachment({\n\t\t\tcursor: cursor ?? 0,\n\t\t\tconnectedAt: Date.now(),\n\t\t});\n\n\t\t// Backfill if cursor provided\n\t\tif (cursor !== null) {\n\t\t\tawait this.backfillFirehose(server, cursor);\n\t\t}\n\n\t\treturn new Response(null, {\n\t\t\tstatus: 101,\n\t\t\twebSocket: client,\n\t\t});\n\t}\n\n\t/**\n\t * WebSocket message handler (hibernation API).\n\t */\n\toverride webSocketMessage(\n\t\t_ws: WebSocket,\n\t\t_message: string | ArrayBuffer,\n\t): void {\n\t\t// Firehose is server-push only, ignore client messages\n\t}\n\n\t/**\n\t * WebSocket close handler (hibernation API).\n\t */\n\toverride webSocketClose(\n\t\t_ws: WebSocket,\n\t\t_code: number,\n\t\t_reason: string,\n\t\t_wasClean: boolean,\n\t): void {\n\t\t// Cleanup handled automatically by hibernation API\n\t}\n\n\t/**\n\t * WebSocket error handler (hibernation API).\n\t */\n\toverride webSocketError(_ws: WebSocket, error: Error): void {\n\t\tconsole.error(\"WebSocket error:\", error);\n\t}\n\n\t/**\n\t * RPC method: Get user preferences\n\t */\n\tasync rpcGetPreferences(): Promise<{ preferences: unknown[] }> {\n\t\tconst storage = await this.getStorage();\n\t\tconst preferences = await storage.getPreferences();\n\t\treturn { preferences };\n\t}\n\n\t/**\n\t * RPC method: Put user preferences\n\t */\n\tasync rpcPutPreferences(preferences: unknown[]): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tawait storage.putPreferences(preferences);\n\t}\n\n\t/**\n\t * RPC method: Get account activation state\n\t */\n\tasync rpcGetActive(): Promise<boolean> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.getActive();\n\t}\n\n\t/**\n\t * RPC method: Activate account\n\t */\n\tasync rpcActivateAccount(): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tawait storage.setActive(true);\n\t}\n\n\t/**\n\t * RPC method: Deactivate account\n\t */\n\tasync rpcDeactivateAccount(): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tawait storage.setActive(false);\n\t}\n\n\t// ============================================\n\t// Migration Progress RPC Methods\n\t// ============================================\n\n\t/**\n\t * RPC method: Count blocks in storage\n\t */\n\tasync rpcCountBlocks(): Promise<number> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.countBlocks();\n\t}\n\n\t/**\n\t * RPC method: Count records in repository\n\t */\n\tasync rpcCountRecords(): Promise<number> {\n\t\tconst repo = await this.getRepo();\n\t\tlet count = 0;\n\t\tfor await (const _record of repo.walkRecords()) {\n\t\t\tcount++;\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * RPC method: Count expected blobs (referenced in records)\n\t */\n\tasync rpcCountExpectedBlobs(): Promise<number> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.countExpectedBlobs();\n\t}\n\n\t/**\n\t * RPC method: Count imported blobs\n\t */\n\tasync rpcCountImportedBlobs(): Promise<number> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.countImportedBlobs();\n\t}\n\n\t/**\n\t * RPC method: List missing blobs (referenced but not imported)\n\t */\n\tasync rpcListMissingBlobs(\n\t\tlimit: number = 500,\n\t\tcursor?: string,\n\t): Promise<{ blobs: Array<{ cid: string; recordUri: string }>; cursor?: string }> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.listMissingBlobs(limit, cursor);\n\t}\n\n\t/**\n\t * RPC method: Reset migration state.\n\t * Clears imported repo and blob tracking to allow re-import.\n\t * Only works when account is deactivated.\n\t */\n\tasync rpcResetMigration(): Promise<{\n\t\tblocksDeleted: number;\n\t\tblobsCleared: number;\n\t}> {\n\t\tconst storage = await this.getStorage();\n\n\t\t// Only allow reset on deactivated accounts\n\t\tconst isActive = await storage.getActive();\n\t\tif (isActive) {\n\t\t\tthrow new Error(\n\t\t\t\t\"AccountActive: Cannot reset migration on an active account. Deactivate first.\",\n\t\t\t);\n\t\t}\n\n\t\t// Get counts before deletion for reporting\n\t\tconst blocksDeleted = await storage.countBlocks();\n\t\tconst blobsCleared = storage.countImportedBlobs();\n\n\t\t// Clear all blocks and reset repo state\n\t\tawait storage.destroy();\n\n\t\t// Clear blob tracking tables\n\t\tstorage.clearBlobTracking();\n\n\t\t// Reset in-memory repo reference so it gets reinitialized on next access\n\t\tthis.repo = null;\n\t\tthis.repoInitialized = false;\n\n\t\treturn { blocksDeleted, blobsCleared };\n\t}\n\n\t/**\n\t * Emit an identity event to notify downstream services to refresh identity cache.\n\t */\n\tasync rpcEmitIdentityEvent(handle: string): Promise<{ seq: number }> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\tconst time = new Date().toISOString();\n\n\t\t// Get next sequence number\n\t\tconst result = this.ctx.storage.sql\n\t\t\t.exec(\n\t\t\t\t`INSERT INTO firehose_events (event_type, payload)\n\t\t\t\t VALUES ('identity', ?)\n\t\t\t\t RETURNING seq`,\n\t\t\t\tnew Uint8Array(0), // Empty payload, we just need seq\n\t\t\t)\n\t\t\t.one();\n\t\tconst seq = result.seq as number;\n\n\t\t// Build identity event frame\n\t\tconst header = { op: 1, t: \"#identity\" };\n\t\tconst body = {\n\t\t\tseq,\n\t\t\tdid: this.env.DID,\n\t\t\ttime,\n\t\t\thandle,\n\t\t};\n\n\t\tconst headerBytes = cborEncode(\n\t\t\theader as unknown as import(\"@atproto/lex-cbor\").LexValue,\n\t\t);\n\t\tconst bodyBytes = cborEncode(\n\t\t\tbody as unknown as import(\"@atproto/lex-cbor\").LexValue,\n\t\t);\n\t\tconst frame = new Uint8Array(headerBytes.length + bodyBytes.length);\n\t\tframe.set(headerBytes, 0);\n\t\tframe.set(bodyBytes, headerBytes.length);\n\n\t\t// Broadcast to all connected clients\n\t\tfor (const ws of this.ctx.getWebSockets()) {\n\t\t\ttry {\n\t\t\t\tws.send(frame);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(\"Error broadcasting identity event:\", e);\n\t\t\t}\n\t\t}\n\n\t\treturn { seq };\n\t}\n\n\t// ============================================\n\t// OAuth Storage RPC Methods\n\t// These methods proxy to SqliteOAuthStorage since we can't serialize the storage object\n\t// ============================================\n\n\t/** Save an authorization code */\n\tasync rpcSaveAuthCode(\n\t\tcode: string,\n\t\tdata: import(\"@getcirrus/oauth-provider\").AuthCodeData,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.saveAuthCode(code, data);\n\t}\n\n\t/** Get authorization code data */\n\tasync rpcGetAuthCode(\n\t\tcode: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").AuthCodeData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getAuthCode(code);\n\t}\n\n\t/** Delete an authorization code */\n\tasync rpcDeleteAuthCode(code: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.deleteAuthCode(code);\n\t}\n\n\t/** Save token data */\n\tasync rpcSaveTokens(\n\t\tdata: import(\"@getcirrus/oauth-provider\").TokenData,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.saveTokens(data);\n\t}\n\n\t/** Get token data by access token */\n\tasync rpcGetTokenByAccess(\n\t\taccessToken: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").TokenData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getTokenByAccess(accessToken);\n\t}\n\n\t/** Get token data by refresh token */\n\tasync rpcGetTokenByRefresh(\n\t\trefreshToken: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").TokenData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getTokenByRefresh(refreshToken);\n\t}\n\n\t/** Revoke a token */\n\tasync rpcRevokeToken(accessToken: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.revokeToken(accessToken);\n\t}\n\n\t/** Revoke all tokens for a user */\n\tasync rpcRevokeAllTokens(sub: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.revokeAllTokens(sub);\n\t}\n\n\t/** Save client metadata */\n\tasync rpcSaveClient(\n\t\tclientId: string,\n\t\tmetadata: import(\"@getcirrus/oauth-provider\").ClientMetadata,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.saveClient(clientId, metadata);\n\t}\n\n\t/** Get client metadata */\n\tasync rpcGetClient(\n\t\tclientId: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").ClientMetadata | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getClient(clientId);\n\t}\n\n\t/** Save PAR data */\n\tasync rpcSavePAR(\n\t\trequestUri: string,\n\t\tdata: import(\"@getcirrus/oauth-provider\").PARData,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.savePAR(requestUri, data);\n\t}\n\n\t/** Get PAR data */\n\tasync rpcGetPAR(\n\t\trequestUri: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").PARData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getPAR(requestUri);\n\t}\n\n\t/** Delete PAR data */\n\tasync rpcDeletePAR(requestUri: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.deletePAR(requestUri);\n\t}\n\n\t/** Check and save DPoP nonce */\n\tasync rpcCheckAndSaveNonce(nonce: string): Promise<boolean> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.checkAndSaveNonce(nonce);\n\t}\n\n\t/**\n\t * HTTP fetch handler for WebSocket upgrades.\n\t * This is used instead of RPC to avoid WebSocket serialization errors.\n\t */\n\toverride async fetch(request: Request): Promise<Response> {\n\t\t// Only handle WebSocket upgrades via fetch\n\t\tconst url = new URL(request.url);\n\t\tif (url.pathname === \"/xrpc/com.atproto.sync.subscribeRepos\") {\n\t\t\treturn this.handleFirehoseUpgrade(request);\n\t\t}\n\n\t\t// All other requests should use RPC methods, not fetch\n\t\treturn new Response(\"Method not allowed\", { status: 405 });\n\t}\n}\n\n/**\n * Serialize a record for JSON by converting CID objects to { $link: \"...\" } format.\n * CBOR-decoded records contain raw CID objects that need conversion for JSON serialization.\n */\nfunction serializeRecord(obj: unknown): unknown {\n\tif (obj === null || obj === undefined) return obj;\n\n\t// Check if this is a CID object using @atproto/lex-data helper\n\tconst cid = asCid(obj);\n\tif (cid) {\n\t\treturn { $link: cid.toString() };\n\t}\n\n\tif (Array.isArray(obj)) {\n\t\treturn obj.map(serializeRecord);\n\t}\n\n\tif (typeof obj === \"object\") {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, value] of Object.entries(obj)) {\n\t\t\tresult[key] = serializeRecord(value);\n\t\t}\n\t\treturn result;\n\t}\n\n\treturn obj;\n}\n\n/**\n * Extract blob CIDs from a record by recursively searching for blob references.\n * Blob refs have the structure: { $type: \"blob\", ref: CID, mimeType, size }\n */\nfunction extractBlobCids(obj: unknown): string[] {\n\tconst cids: string[] = [];\n\n\tfunction walk(value: unknown): void {\n\t\tif (value === null || value === undefined) return;\n\n\t\t// Check if this is a blob reference using @atproto/lex-data helper\n\t\tif (isBlobRef(value)) {\n\t\t\tcids.push(value.ref.toString());\n\t\t\treturn; // No need to recurse into blob ref properties\n\t\t}\n\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\twalk(item);\n\t\t\t}\n\t\t} else if (typeof value === \"object\") {\n\t\t\t// Recursively walk all properties\n\t\t\tfor (const key of Object.keys(value as Record<string, unknown>)) {\n\t\t\t\twalk((value as Record<string, unknown>)[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\twalk(obj);\n\treturn cids;\n}\n","import { Secp256k1Keypair, randomStr, verifySignature } from \"@atproto/crypto\";\n\nconst MINUTE = 60 * 1000;\n\n/**\n * Shared keypair cache for signing and verification.\n */\nlet cachedKeypair: Secp256k1Keypair | null = null;\nlet cachedSigningKey: string | null = null;\n\n/**\n * Get the signing keypair, with caching.\n * Used for creating service JWTs and verifying them.\n */\nexport async function getSigningKeypair(\n\tsigningKey: string,\n): Promise<Secp256k1Keypair> {\n\tif (cachedKeypair && cachedSigningKey === signingKey) {\n\t\treturn cachedKeypair;\n\t}\n\tcachedKeypair = await Secp256k1Keypair.import(signingKey);\n\tcachedSigningKey = signingKey;\n\treturn cachedKeypair;\n}\n\n/**\n * Service JWT payload structure\n */\nexport interface ServiceJwtPayload {\n\tiss: string; // Issuer (user's DID)\n\taud: string; // Audience (PDS DID)\n\texp: number; // Expiration timestamp\n\tiat?: number; // Issued at timestamp\n\tlxm?: string; // Lexicon method (optional)\n\tjti?: string; // Token ID (optional)\n}\n\ntype ServiceJwtParams = {\n\tiss: string;\n\taud: string;\n\tlxm: string | null;\n\tkeypair: Secp256k1Keypair;\n};\n\nfunction jsonToB64Url(json: Record<string, unknown>): string {\n\treturn Buffer.from(JSON.stringify(json)).toString(\"base64url\");\n}\n\nfunction noUndefinedVals<T extends Record<string, unknown>>(\n\tobj: T,\n): Partial<T> {\n\tconst result: Partial<T> = {};\n\tfor (const [key, val] of Object.entries(obj)) {\n\t\tif (val !== undefined) {\n\t\t\tresult[key as keyof T] = val as T[keyof T];\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Create a service JWT for proxied requests to AppView.\n * The JWT asserts that the PDS vouches for the user identified by `iss`.\n */\nexport async function createServiceJwt(\n\tparams: ServiceJwtParams,\n): Promise<string> {\n\tconst { iss, aud, keypair } = params;\n\tconst iat = Math.floor(Date.now() / 1000);\n\tconst exp = iat + MINUTE / 1000;\n\tconst lxm = params.lxm ?? undefined;\n\tconst jti = randomStr(16, \"hex\");\n\n\tconst header = {\n\t\ttyp: \"JWT\",\n\t\talg: keypair.jwtAlg,\n\t};\n\n\tconst payload = noUndefinedVals({\n\t\tiat,\n\t\tiss,\n\t\taud,\n\t\texp,\n\t\tlxm,\n\t\tjti,\n\t});\n\n\tconst toSignStr = `${jsonToB64Url(header)}.${jsonToB64Url(payload as Record<string, unknown>)}`;\n\tconst toSign = Buffer.from(toSignStr, \"utf8\");\n\tconst sig = Buffer.from(await keypair.sign(toSign));\n\n\treturn `${toSignStr}.${sig.toString(\"base64url\")}`;\n}\n\n/**\n * Verify a service JWT signed with our signing key.\n * These are issued by getServiceAuth and used by external services\n * (like video.bsky.app) to call back to our PDS.\n */\nexport async function verifyServiceJwt(\n\ttoken: string,\n\tsigningKey: string,\n\texpectedAudience: string,\n\texpectedIssuer: string,\n): Promise<ServiceJwtPayload> {\n\tconst parts = token.split(\".\");\n\tif (parts.length !== 3) {\n\t\tthrow new Error(\"Invalid JWT format\");\n\t}\n\n\tconst headerB64 = parts[0]!;\n\tconst payloadB64 = parts[1]!;\n\tconst signatureB64 = parts[2]!;\n\n\t// Decode header\n\tconst header = JSON.parse(Buffer.from(headerB64, \"base64url\").toString());\n\tif (header.alg !== \"ES256K\") {\n\t\tthrow new Error(`Unsupported algorithm: ${header.alg}`);\n\t}\n\n\t// Decode payload\n\tconst payload: ServiceJwtPayload = JSON.parse(\n\t\tBuffer.from(payloadB64, \"base64url\").toString(),\n\t);\n\n\t// Check expiration\n\tconst now = Math.floor(Date.now() / 1000);\n\tif (payload.exp && payload.exp < now) {\n\t\tthrow new Error(\"Token expired\");\n\t}\n\n\t// Check audience (should be our PDS)\n\tif (payload.aud !== expectedAudience) {\n\t\tthrow new Error(`Invalid audience: expected ${expectedAudience}`);\n\t}\n\n\t// Check issuer (should be the user's DID)\n\tif (payload.iss !== expectedIssuer) {\n\t\tthrow new Error(`Invalid issuer: expected ${expectedIssuer}`);\n\t}\n\n\t// Verify signature using shared keypair\n\tconst keypair = await getSigningKeypair(signingKey);\n\t// Uint8Array wrapper is required - Buffer polyfill doesn't work with @atproto/crypto\n\tconst msgBytes = new Uint8Array(\n\t\tBuffer.from(`${headerB64}.${payloadB64}`, \"utf8\"),\n\t);\n\tconst sigBytes = new Uint8Array(Buffer.from(signatureB64, \"base64url\"));\n\n\tconst isValid = await verifySignature(keypair.did(), msgBytes, sigBytes, {\n\t\tallowMalleableSig: true,\n\t});\n\n\tif (!isValid) {\n\t\tthrow new Error(\"Invalid signature\");\n\t}\n\n\treturn payload;\n}\n","import { SignJWT, jwtVerify, type JWTPayload } from \"jose\";\nimport { compare } from \"bcryptjs\";\n\nconst ACCESS_TOKEN_LIFETIME = \"2h\";\nconst REFRESH_TOKEN_LIFETIME = \"90d\";\n\n/**\n * Create a secret key from string for HS256 signing\n */\nfunction createSecretKey(secret: string): Uint8Array {\n\treturn new TextEncoder().encode(secret);\n}\n\n/**\n * Create an access token (short-lived, 2 hours)\n */\nexport async function createAccessToken(\n\tjwtSecret: string,\n\tuserDid: string,\n\tserviceDid: string,\n): Promise<string> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\treturn new SignJWT({ scope: \"atproto\" })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"at+jwt\" })\n\t\t.setIssuedAt()\n\t\t.setIssuer(serviceDid)\n\t\t.setAudience(serviceDid)\n\t\t.setSubject(userDid)\n\t\t.setExpirationTime(ACCESS_TOKEN_LIFETIME)\n\t\t.sign(secret);\n}\n\n/**\n * Create a refresh token (long-lived, 90 days)\n */\nexport async function createRefreshToken(\n\tjwtSecret: string,\n\tuserDid: string,\n\tserviceDid: string,\n): Promise<string> {\n\tconst secret = createSecretKey(jwtSecret);\n\tconst jti = crypto.randomUUID();\n\n\treturn new SignJWT({ scope: \"com.atproto.refresh\", jti })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"refresh+jwt\" })\n\t\t.setIssuedAt()\n\t\t.setIssuer(serviceDid)\n\t\t.setAudience(serviceDid)\n\t\t.setSubject(userDid)\n\t\t.setExpirationTime(REFRESH_TOKEN_LIFETIME)\n\t\t.sign(secret);\n}\n\n/**\n * Verify an access token and return the payload\n */\nexport async function verifyAccessToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tconst { payload, protectedHeader } = await jwtVerify(token, secret, {\n\t\tissuer: serviceDid,\n\t\taudience: serviceDid,\n\t});\n\n\t// Check token type\n\tif (protectedHeader.typ !== \"at+jwt\") {\n\t\tthrow new Error(\"Invalid token type\");\n\t}\n\n\t// Check scope\n\tif (payload.scope !== \"atproto\") {\n\t\tthrow new Error(\"Invalid scope\");\n\t}\n\n\treturn payload;\n}\n\n/**\n * Verify a refresh token and return the payload\n */\nexport async function verifyRefreshToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tconst { payload, protectedHeader } = await jwtVerify(token, secret, {\n\t\tissuer: serviceDid,\n\t\taudience: serviceDid,\n\t});\n\n\t// Check token type\n\tif (protectedHeader.typ !== \"refresh+jwt\") {\n\t\tthrow new Error(\"Invalid token type\");\n\t}\n\n\t// Check scope\n\tif (payload.scope !== \"com.atproto.refresh\") {\n\t\tthrow new Error(\"Invalid scope\");\n\t}\n\n\t// Require token ID\n\tif (!payload.jti) {\n\t\tthrow new Error(\"Missing token ID\");\n\t}\n\n\treturn payload;\n}\n\n/**\n * Verify a password against a bcrypt hash\n */\nexport { compare as verifyPassword };\n","/**\n * OAuth 2.1 integration for the PDS\n *\n * Connects the @getcirrus/oauth-provider package with the PDS\n * by providing storage through Durable Objects and user authentication\n * through the existing session system.\n */\n\nimport { Hono } from \"hono\";\nimport { ATProtoOAuthProvider } from \"@getcirrus/oauth-provider\";\nimport type {\n\tOAuthStorage,\n\tAuthCodeData,\n\tTokenData,\n\tClientMetadata,\n\tPARData,\n} from \"@getcirrus/oauth-provider\";\nimport { compare } from \"bcryptjs\";\nimport type { PDSEnv } from \"./types\";\nimport type { AccountDurableObject } from \"./account-do\";\n\n/**\n * Proxy storage class that delegates to DO RPC methods\n *\n * This is needed because SqliteOAuthStorage instances contain a SQL connection\n * that can't be serialized across the DO RPC boundary. Instead, we delegate each\n * storage operation to individual RPC methods that pass only serializable data.\n */\nclass DOProxyOAuthStorage implements OAuthStorage {\n\tconstructor(private accountDO: DurableObjectStub<AccountDurableObject>) {}\n\n\tasync saveAuthCode(code: string, data: AuthCodeData): Promise<void> {\n\t\tawait this.accountDO.rpcSaveAuthCode(code, data);\n\t}\n\n\tasync getAuthCode(code: string): Promise<AuthCodeData | null> {\n\t\treturn this.accountDO.rpcGetAuthCode(code);\n\t}\n\n\tasync deleteAuthCode(code: string): Promise<void> {\n\t\tawait this.accountDO.rpcDeleteAuthCode(code);\n\t}\n\n\tasync saveTokens(data: TokenData): Promise<void> {\n\t\tawait this.accountDO.rpcSaveTokens(data);\n\t}\n\n\tasync getTokenByAccess(accessToken: string): Promise<TokenData | null> {\n\t\treturn this.accountDO.rpcGetTokenByAccess(accessToken);\n\t}\n\n\tasync getTokenByRefresh(refreshToken: string): Promise<TokenData | null> {\n\t\treturn this.accountDO.rpcGetTokenByRefresh(refreshToken);\n\t}\n\n\tasync revokeToken(accessToken: string): Promise<void> {\n\t\tawait this.accountDO.rpcRevokeToken(accessToken);\n\t}\n\n\tasync revokeAllTokens(sub: string): Promise<void> {\n\t\tawait this.accountDO.rpcRevokeAllTokens(sub);\n\t}\n\n\tasync saveClient(clientId: string, metadata: ClientMetadata): Promise<void> {\n\t\tawait this.accountDO.rpcSaveClient(clientId, metadata);\n\t}\n\n\tasync getClient(clientId: string): Promise<ClientMetadata | null> {\n\t\treturn this.accountDO.rpcGetClient(clientId);\n\t}\n\n\tasync savePAR(requestUri: string, data: PARData): Promise<void> {\n\t\tawait this.accountDO.rpcSavePAR(requestUri, data);\n\t}\n\n\tasync getPAR(requestUri: string): Promise<PARData | null> {\n\t\treturn this.accountDO.rpcGetPAR(requestUri);\n\t}\n\n\tasync deletePAR(requestUri: string): Promise<void> {\n\t\tawait this.accountDO.rpcDeletePAR(requestUri);\n\t}\n\n\tasync checkAndSaveNonce(nonce: string): Promise<boolean> {\n\t\treturn this.accountDO.rpcCheckAndSaveNonce(nonce);\n\t}\n}\n\n/**\n * Get the OAuth provider for the given environment\n * Exported for use in auth middleware for token verification\n */\nexport function getProvider(env: PDSEnv): ATProtoOAuthProvider {\n\tconst accountDO = getAccountDO(env);\n\tconst storage = new DOProxyOAuthStorage(accountDO);\n\tconst issuer = `https://${env.PDS_HOSTNAME}`;\n\n\treturn new ATProtoOAuthProvider({\n\t\tstorage,\n\t\tissuer,\n\t\tdpopRequired: true,\n\t\tenablePAR: true,\n\t\t// Password verification for authorization\n\t\tverifyUser: async (password: string) => {\n\t\t\tconst valid = await compare(password, env.PASSWORD_HASH);\n\t\t\tif (!valid) return null;\n\t\t\treturn {\n\t\t\t\tsub: env.DID,\n\t\t\t\thandle: env.HANDLE,\n\t\t\t};\n\t\t},\n\t});\n}\n\n// Module-level reference to getAccountDO for the exported getProvider function\nlet getAccountDO: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>;\n\n/**\n * Create OAuth routes for the PDS\n *\n * This creates a Hono sub-app with all OAuth endpoints:\n * - GET /.well-known/oauth-authorization-server - Server metadata\n * - GET /oauth/authorize - Authorization endpoint\n * - POST /oauth/authorize - Handle authorization consent\n * - POST /oauth/token - Token endpoint\n * - POST /oauth/par - Pushed Authorization Request\n *\n * @param accountDOGetter Function to get the account DO stub\n */\nexport function createOAuthApp(\n\taccountDOGetter: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>,\n) {\n\t// Store reference for the exported getProvider function\n\tgetAccountDO = accountDOGetter;\n\n\tconst oauth = new Hono<{ Bindings: PDSEnv }>();\n\n\t// OAuth server metadata\n\toauth.get(\"/.well-known/oauth-authorization-server\", (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleMetadata();\n\t});\n\n\t// Protected resource metadata (for token introspection discovery)\n\toauth.get(\"/.well-known/oauth-protected-resource\", (c) => {\n\t\tconst issuer = `https://${c.env.PDS_HOSTNAME}`;\n\t\treturn c.json({\n\t\t\tresource: issuer,\n\t\t\tauthorization_servers: [issuer],\n\t\t\tscopes_supported: [\n\t\t\t\t\"atproto\",\n\t\t\t\t\"transition:generic\",\n\t\t\t\t\"transition:chat.bsky\",\n\t\t\t],\n\t\t});\n\t});\n\n\t// Authorization endpoint\n\toauth.get(\"/oauth/authorize\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleAuthorize(c.req.raw);\n\t});\n\n\toauth.post(\"/oauth/authorize\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleAuthorize(c.req.raw);\n\t});\n\n\t// Token endpoint\n\toauth.post(\"/oauth/token\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleToken(c.req.raw);\n\t});\n\n\t// Pushed Authorization Request endpoint\n\toauth.post(\"/oauth/par\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handlePAR(c.req.raw);\n\t});\n\n\t// Token revocation endpoint\n\toauth.post(\"/oauth/revoke\", async (c) => {\n\t\t// Parse the token from the request\n\t\t// RFC 7009 requires application/x-www-form-urlencoded, we also accept JSON\n\t\tconst contentType = c.req.header(\"Content-Type\") ?? \"\";\n\t\tlet token: string | undefined;\n\n\t\ttry {\n\t\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t\tconst json = await c.req.json();\n\t\t\t\ttoken = json.token;\n\t\t\t} else if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n\t\t\t\tconst body = await c.req.text();\n\t\t\t\tconst params = Object.fromEntries(new URLSearchParams(body).entries());\n\t\t\t\ttoken = params.token;\n\t\t\t} else if (!contentType) {\n\t\t\t\t// No Content-Type: treat as empty body (no token)\n\t\t\t\ttoken = undefined;\n\t\t\t} else {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"invalid_request\",\n\t\t\t\t\t\terror_description:\n\t\t\t\t\t\t\t\"Content-Type must be application/x-www-form-urlencoded (per RFC 7009) or application/json\",\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn c.json(\n\t\t\t\t{ error: \"invalid_request\", error_description: \"Failed to parse request body\" },\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\tif (!token) {\n\t\t\t// Per RFC 7009, return 200 even if no token provided\n\t\t\treturn c.json({});\n\t\t}\n\n\t\t// Try to revoke the token (RFC 7009 accepts both access and refresh tokens)\n\t\tconst accountDO = getAccountDO(c.env);\n\n\t\t// First try as access token\n\t\tawait accountDO.rpcRevokeToken(token);\n\n\t\t// Also check if it's a refresh token and revoke the associated access token\n\t\tconst tokenData = await accountDO.rpcGetTokenByRefresh(token);\n\t\tif (tokenData) {\n\t\t\tawait accountDO.rpcRevokeToken(tokenData.accessToken);\n\t\t}\n\n\t\t// Always return success (per RFC 7009)\n\t\treturn c.json({});\n\t});\n\n\treturn oauth;\n}\n","import type { Context, Next } from \"hono\";\nimport { verifyServiceJwt } from \"../service-auth\";\nimport { verifyAccessToken } from \"../session\";\nimport { getProvider } from \"../oauth\";\nimport type { PDSEnv } from \"../types\";\n\nexport interface AuthInfo {\n\tdid: string;\n\tscope: string;\n}\n\nexport type AuthVariables = {\n\tauth: AuthInfo;\n};\n\nexport async function requireAuth(\n\tc: Context<{ Bindings: PDSEnv; Variables: AuthVariables }>,\n\tnext: Next,\n): Promise<Response | void> {\n\tconst auth = c.req.header(\"Authorization\");\n\n\tif (!auth) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthMissing\",\n\t\t\t\tmessage: \"Authorization header required\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\t// Handle DPoP-bound OAuth tokens\n\tif (auth.startsWith(\"DPoP \")) {\n\t\tconst provider = getProvider(c.env);\n\n\t\t// Verify OAuth access token with DPoP proof\n\t\tconst tokenData = await provider.verifyAccessToken(c.req.raw);\n\t\tif (!tokenData) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid OAuth access token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\tc.set(\"auth\", { did: tokenData.sub, scope: tokenData.scope });\n\t\treturn next();\n\t}\n\n\t// Handle Bearer tokens (session JWTs, static token, service JWTs)\n\tif (!auth.startsWith(\"Bearer \")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthMissing\",\n\t\t\t\tmessage: \"Invalid authorization scheme\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\tconst token = auth.slice(7);\n\n\t// Try static token first (backwards compatibility)\n\tif (token === c.env.AUTH_TOKEN) {\n\t\tc.set(\"auth\", { did: c.env.DID, scope: \"atproto\" });\n\t\treturn next();\n\t}\n\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\t// Try session JWT verification (HS256, signed with JWT_SECRET)\n\t// Used by Bluesky app for normal operations (posts, likes, etc.)\n\ttry {\n\t\tconst payload = await verifyAccessToken(\n\t\t\ttoken,\n\t\t\tc.env.JWT_SECRET,\n\t\t\tserviceDid,\n\t\t);\n\n\t\t// Verify subject matches our DID\n\t\tif (payload.sub !== c.env.DID) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid access token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\t// Store auth info in context for downstream use\n\t\tc.set(\"auth\", { did: payload.sub, scope: payload.scope as string });\n\t\treturn next();\n\t} catch {\n\t\t// Session JWT verification failed, try service JWT\n\t}\n\n\t// Try service JWT verification (ES256K, signed with our signing key)\n\t// Used by external services (like video.bsky.app) calling back to our PDS\n\ttry {\n\t\tconst payload = await verifyServiceJwt(\n\t\t\ttoken,\n\t\t\tc.env.SIGNING_KEY,\n\t\t\tserviceDid, // audience should be our PDS\n\t\t\tc.env.DID, // issuer should be the user's DID\n\t\t);\n\n\t\t// Store auth info in context\n\t\tc.set(\"auth\", { did: payload.iss, scope: payload.lxm || \"atproto\" });\n\t\treturn next();\n\t} catch {\n\t\t// Service JWT verification also failed\n\t}\n\n\treturn c.json(\n\t\t{\n\t\t\terror: \"AuthenticationRequired\",\n\t\t\tmessage: \"Invalid authentication token\",\n\t\t},\n\t\t401,\n\t);\n}\n","/**\n * DID resolution for Cloudflare Workers\n *\n * We can't use @atproto/identity directly because it uses `redirect: \"error\"`\n * which Cloudflare Workers doesn't support. This is a simple implementation\n * that's compatible with Workers.\n */\n\nimport { check, didDocument, type DidDocument } from \"@atproto/common-web\";\nimport type { DidCache } from \"@atproto/identity\";\n\nconst PLC_DIRECTORY = \"https://plc.directory\";\nconst TIMEOUT_MS = 3000;\n\nexport interface DidResolverOpts {\n\tplcUrl?: string;\n\ttimeout?: number;\n\tdidCache?: DidCache;\n}\n\nexport class DidResolver {\n\tprivate plcUrl: string;\n\tprivate timeout: number;\n\tprivate cache?: DidCache;\n\n\tconstructor(opts: DidResolverOpts = {}) {\n\t\tthis.plcUrl = opts.plcUrl ?? PLC_DIRECTORY;\n\t\tthis.timeout = opts.timeout ?? TIMEOUT_MS;\n\t\tthis.cache = opts.didCache;\n\t}\n\n\tasync resolve(did: string): Promise<DidDocument | null> {\n\t\t// Check cache first\n\t\tif (this.cache) {\n\t\t\tconst cached = await this.cache.checkCache(did);\n\t\t\tif (cached && !cached.expired) {\n\t\t\t\t// Trigger background refresh if stale\n\t\t\t\tif (cached.stale) {\n\t\t\t\t\tthis.cache.refreshCache(did, () => this.resolveNoCache(did), cached);\n\t\t\t\t}\n\t\t\t\treturn cached.doc;\n\t\t\t}\n\t\t}\n\n\t\tconst doc = await this.resolveNoCache(did);\n\n\t\t// Update cache\n\t\tif (doc && this.cache) {\n\t\t\tawait this.cache.cacheDid(did, doc);\n\t\t} else if (!doc && this.cache) {\n\t\t\tawait this.cache.clearEntry(did);\n\t\t}\n\n\t\treturn doc;\n\t}\n\n\tprivate async resolveNoCache(did: string): Promise<DidDocument | null> {\n\t\tif (did.startsWith(\"did:web:\")) {\n\t\t\treturn this.resolveDidWeb(did);\n\t\t}\n\t\tif (did.startsWith(\"did:plc:\")) {\n\t\t\treturn this.resolveDidPlc(did);\n\t\t}\n\t\tthrow new Error(`Unsupported DID method: ${did}`);\n\t}\n\n\tprivate async resolveDidWeb(did: string): Promise<DidDocument | null> {\n\t\tconst parts = did.split(\":\").slice(2);\n\t\tif (parts.length === 0) {\n\t\t\tthrow new Error(`Invalid did:web format: ${did}`);\n\t\t}\n\n\t\t// Only support simple did:web without paths (like @atproto/identity)\n\t\tif (parts.length > 1) {\n\t\t\tthrow new Error(`Unsupported did:web with path: ${did}`);\n\t\t}\n\n\t\tconst domain = decodeURIComponent(parts[0]!);\n\t\tconst url = new URL(`https://${domain}/.well-known/did.json`);\n\n\t\t// Use http for localhost\n\t\tif (url.hostname === \"localhost\") {\n\t\t\turl.protocol = \"http:\";\n\t\t}\n\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url.toString(), {\n\t\t\t\tsignal: controller.signal,\n\t\t\t\tredirect: \"manual\", // Workers doesn't support \"error\"\n\t\t\t\theaders: { accept: \"application/did+ld+json,application/json\" },\n\t\t\t});\n\n\t\t\t// Check for redirect (we don't follow them for security)\n\t\t\tif (res.status >= 300 && res.status < 400) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (!res.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst doc = await res.json();\n\t\t\treturn this.validateDidDoc(did, doc);\n\t\t} finally {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t}\n\n\tprivate async resolveDidPlc(did: string): Promise<DidDocument | null> {\n\t\tconst url = new URL(`/${encodeURIComponent(did)}`, this.plcUrl);\n\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url.toString(), {\n\t\t\t\tsignal: controller.signal,\n\t\t\t\tredirect: \"manual\", // Workers doesn't support \"error\"\n\t\t\t\theaders: { accept: \"application/did+ld+json,application/json\" },\n\t\t\t});\n\n\t\t\t// Check for redirect (we don't follow them for security)\n\t\t\tif (res.status >= 300 && res.status < 400) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (res.status === 404) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (!res.ok) {\n\t\t\t\tthrow new Error(`PLC directory error: ${res.status} ${res.statusText}`);\n\t\t\t}\n\n\t\t\tconst doc = (await res.json()) as DidDocument;\n\t\t\treturn this.validateDidDoc(did, doc);\n\t\t} finally {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t}\n\n\tprivate validateDidDoc(did: string, doc: unknown): DidDocument | null {\n\t\tif (!check.is(doc, didDocument)) {\n\t\t\treturn null;\n\t\t}\n\t\tif (doc.id !== did) {\n\t\t\treturn null;\n\t\t}\n\t\treturn doc;\n\t}\n}\n","/**\n * DID cache using Cloudflare Workers Cache API\n */\n\nimport type { DidCache, CacheResult, DidDocument } from \"@atproto/identity\";\nimport { check, didDocument } from \"@atproto/common-web\";\nimport { waitUntil } from \"cloudflare:workers\";\n\nconst STALE_TTL = 60 * 60 * 1000; // 1 hour - serve from cache but refresh in background\nconst MAX_TTL = 24 * 60 * 60 * 1000; // 24 hours - must refresh\n\nexport class WorkersDidCache implements DidCache {\n\tprivate cache: Cache;\n\n\tconstructor() {\n\t\tthis.cache = caches.default;\n\t}\n\n\tprivate getCacheKey(did: string): string {\n\t\t// Use a stable URL format for cache keys\n\t\treturn `https://did-cache.internal/${encodeURIComponent(did)}`;\n\t}\n\n\tasync cacheDid(\n\t\tdid: string,\n\t\tdoc: DidDocument,\n\t\t_prevResult?: CacheResult,\n\t): Promise<void> {\n\t\tconst cacheKey = this.getCacheKey(did);\n\t\tconst response = new Response(JSON.stringify(doc), {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"Cache-Control\": \"max-age=86400\", // 24 hours\n\t\t\t\t\"X-Cached-At\": Date.now().toString(),\n\t\t\t},\n\t\t});\n\n\t\tawait this.cache.put(cacheKey, response);\n\t}\n\n\tasync checkCache(did: string): Promise<CacheResult | null> {\n\t\tconst cacheKey = this.getCacheKey(did);\n\t\tconst response = await this.cache.match(cacheKey);\n\n\t\tif (!response) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst cachedAt = parseInt(response.headers.get(\"X-Cached-At\") || \"0\", 10);\n\t\tconst now = Date.now();\n\t\tconst age = now - cachedAt;\n\n\t\tconst doc = await response.json();\n\n\t\t// Validate cached document schema\n\t\tif (!check.is(doc, didDocument) || doc.id !== did) {\n\t\t\tawait this.clearEntry(did);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tdid,\n\t\t\tdoc,\n\t\t\tupdatedAt: cachedAt,\n\t\t\tstale: age > STALE_TTL,\n\t\t\texpired: age > MAX_TTL,\n\t\t};\n\t}\n\n\tasync refreshCache(\n\t\tdid: string,\n\t\tgetDoc: () => Promise<DidDocument | null>,\n\t\t_prevResult?: CacheResult,\n\t): Promise<void> {\n\t\t// Background refresh using waitUntil to ensure it completes after response\n\t\twaitUntil(\n\t\t\tgetDoc().then((doc) => {\n\t\t\t\tif (doc) {\n\t\t\t\t\treturn this.cacheDid(did, doc);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tasync clearEntry(did: string): Promise<void> {\n\t\tconst cacheKey = this.getCacheKey(did);\n\t\tawait this.cache.delete(cacheKey);\n\t}\n\n\tasync clear(): Promise<void> {\n\t\t// Cache API doesn't have a clear-all method\n\t\t// Would need to track keys separately if needed\n\t\t// For now, entries will expire naturally\n\t}\n}\n","/**\n * XRPC service proxying with atproto-proxy header support\n * See: https://atproto.com/specs/xrpc#service-proxying\n */\n\nimport type { Context } from \"hono\";\nimport { DidResolver } from \"./did-resolver\";\nimport { getServiceEndpoint } from \"@atproto/common-web\";\nimport { createServiceJwt } from \"./service-auth\";\nimport { verifyAccessToken } from \"./session\";\nimport { getProvider } from \"./oauth\";\nimport type { PDSEnv } from \"./types\";\nimport type { Secp256k1Keypair } from \"@atproto/crypto\";\n\n/**\n * Parse atproto-proxy header value\n * Format: \"did:web:example.com#service_id\"\n * Returns: { did: \"did:web:example.com\", serviceId: \"service_id\" }\n */\nexport function parseProxyHeader(\n\theader: string,\n): { did: string; serviceId: string } | null {\n\tconst parts = header.split(\"#\");\n\tif (parts.length !== 2) {\n\t\treturn null;\n\t}\n\n\tconst [did, serviceId] = parts;\n\tif (!did?.startsWith(\"did:\") || !serviceId) {\n\t\treturn null;\n\t}\n\n\treturn { did, serviceId };\n}\n\n/**\n * Handle XRPC proxy requests\n * Routes requests to external services based on atproto-proxy header or lexicon namespace\n */\nexport async function handleXrpcProxy(\n\tc: Context<{ Bindings: PDSEnv }>,\n\tdidResolver: DidResolver,\n\tgetKeypair: () => Promise<Secp256k1Keypair>,\n): Promise<Response> {\n\t// Extract XRPC method name from path (e.g., \"app.bsky.feed.getTimeline\")\n\tconst url = new URL(c.req.url);\n\tconst lxm = url.pathname.replace(\"/xrpc/\", \"\");\n\n\t// Validate XRPC path to prevent path traversal\n\tif (lxm.includes(\"..\") || lxm.includes(\"//\")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Invalid XRPC method path\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Check for atproto-proxy header for explicit service routing\n\tconst proxyHeader = c.req.header(\"atproto-proxy\");\n\tlet audienceDid: string;\n\tlet targetUrl: URL;\n\n\tif (proxyHeader) {\n\t\t// Parse proxy header: \"did:web:example.com#service_id\"\n\t\tconst parsed = parseProxyHeader(proxyHeader);\n\t\tif (!parsed) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\tmessage: `Invalid atproto-proxy header format: ${proxyHeader}`,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// Resolve DID document to get service endpoint (with caching)\n\t\t\tconst didDoc = await didResolver.resolve(parsed.did);\n\t\t\tif (!didDoc) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\t\tmessage: `DID not found: ${parsed.did}`,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// getServiceEndpoint expects the ID to start with #\n\t\t\tconst serviceId = parsed.serviceId.startsWith(\"#\")\n\t\t\t\t? parsed.serviceId\n\t\t\t\t: `#${parsed.serviceId}`;\n\t\t\tconst endpoint = getServiceEndpoint(didDoc, { id: serviceId });\n\n\t\t\tif (!endpoint) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\t\tmessage: `Service not found in DID document: ${parsed.serviceId}`,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Use the resolved service endpoint\n\t\t\taudienceDid = parsed.did;\n\t\t\ttargetUrl = new URL(endpoint);\n\t\t\tif (targetUrl.protocol !== \"https:\") {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\t\tmessage: \"Proxy target must use HTTPS\",\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t\ttargetUrl.pathname = url.pathname;\n\t\t\ttargetUrl.search = url.search;\n\t\t} catch (err) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\tmessage: `Failed to resolve service: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\t} else {\n\t\t// Fallback: Route to Bluesky services based on lexicon namespace\n\t\t// These are well-known endpoints that don't require DID resolution\n\t\tconst isChat = lxm.startsWith(\"chat.bsky.\");\n\t\taudienceDid = isChat ? \"did:web:api.bsky.chat\" : \"did:web:api.bsky.app\";\n\t\tconst endpoint = isChat ? \"https://api.bsky.chat\" : \"https://api.bsky.app\";\n\n\t\t// Construct URL safely using URL constructor\n\t\ttargetUrl = new URL(`/xrpc/${lxm}${url.search}`, endpoint);\n\t}\n\n\t// Verify auth and create service JWT for target service\n\tlet headers: Record<string, string> = {};\n\tconst auth = c.req.header(\"Authorization\");\n\tlet userDid: string | undefined;\n\n\tif (auth?.startsWith(\"DPoP \")) {\n\t\t// Verify DPoP-bound OAuth access token\n\t\ttry {\n\t\t\tconst provider = getProvider(c.env);\n\t\t\tconst tokenData = await provider.verifyAccessToken(c.req.raw);\n\t\t\tif (tokenData) {\n\t\t\t\tuserDid = tokenData.sub;\n\t\t\t}\n\t\t} catch {\n\t\t\t// DPoP verification failed - continue without auth\n\t\t}\n\t} else if (auth?.startsWith(\"Bearer \")) {\n\t\tconst token = auth.slice(7);\n\t\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\t\ttry {\n\t\t\t// Check static token first\n\t\t\tif (token === c.env.AUTH_TOKEN) {\n\t\t\t\tuserDid = c.env.DID;\n\t\t\t} else {\n\t\t\t\t// Verify JWT\n\t\t\t\tconst payload = await verifyAccessToken(\n\t\t\t\t\ttoken,\n\t\t\t\t\tc.env.JWT_SECRET,\n\t\t\t\t\tserviceDid,\n\t\t\t\t);\n\t\t\t\tif (payload.sub) {\n\t\t\t\t\tuserDid = payload.sub;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Token verification failed - continue without auth\n\t\t}\n\t}\n\n\t// Create service JWT if user is authenticated\n\tif (userDid) {\n\t\ttry {\n\t\t\tconst keypair = await getKeypair();\n\t\t\tconst serviceJwt = await createServiceJwt({\n\t\t\t\tiss: userDid,\n\t\t\t\taud: audienceDid,\n\t\t\t\tlxm,\n\t\t\t\tkeypair,\n\t\t\t});\n\t\t\theaders[\"Authorization\"] = `Bearer ${serviceJwt}`;\n\t\t} catch {\n\t\t\t// Service JWT creation failed - forward without auth\n\t\t}\n\t}\n\n\t// Forward request with potentially replaced auth header\n\t// Use Headers object for case-insensitive handling\n\tconst forwardHeaders = new Headers(c.req.raw.headers);\n\n\t// Remove headers that shouldn't be forwarded (security/privacy)\n\tconst headersToRemove = [\n\t\t\"authorization\", // Replaced with service JWT\n\t\t\"atproto-proxy\", // Internal routing header\n\t\t\"host\", // Will be set by fetch\n\t\t\"connection\", // Connection-specific\n\t\t\"cookie\", // Privacy - don't leak cookies\n\t\t\"x-forwarded-for\", // Don't leak client IP\n\t\t\"x-real-ip\", // Don't leak client IP\n\t\t\"x-forwarded-proto\", // Internal\n\t\t\"x-forwarded-host\", // Internal\n\t];\n\n\tfor (const header of headersToRemove) {\n\t\tforwardHeaders.delete(header);\n\t}\n\n\t// Add service auth if we have it\n\tif (headers[\"Authorization\"]) {\n\t\tforwardHeaders.set(\"Authorization\", headers[\"Authorization\"]);\n\t}\n\n\tconst reqInit: RequestInit = {\n\t\tmethod: c.req.method,\n\t\theaders: forwardHeaders,\n\t};\n\n\t// Include body for non-GET requests\n\tif (c.req.method !== \"GET\" && c.req.method !== \"HEAD\") {\n\t\treqInit.body = c.req.raw.body;\n\t}\n\n\treturn fetch(targetUrl.toString(), reqInit);\n}\n","import type { Context } from \"hono\";\nimport { ensureValidDid } from \"@atproto/syntax\";\nimport type { AccountDurableObject } from \"../account-do.js\";\nimport type { AppEnv } from \"../types\";\n\nexport async function getRepo(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst carBytes = await accountDO.rpcGetRepoCar();\n\n\treturn new Response(carBytes, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/vnd.ipld.car\",\n\t\t\t\"Content-Length\": carBytes.length.toString(),\n\t\t},\n\t});\n}\n\nexport async function getRepoStatus(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst data = await accountDO.rpcGetRepoStatus();\n\n\treturn c.json({\n\t\tdid: data.did,\n\t\tactive: true,\n\t\tstatus: \"active\",\n\t\trev: data.rev,\n\t});\n}\n\nexport async function listRepos(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\t// Single-user PDS - just return our one repo\n\tconst data = await accountDO.rpcGetRepoStatus();\n\n\treturn c.json({\n\t\trepos: [\n\t\t\t{\n\t\t\t\tdid: data.did,\n\t\t\t\thead: data.head,\n\t\t\t\trev: data.rev,\n\t\t\t\tactive: true,\n\t\t\t},\n\t\t],\n\t});\n}\n\nexport async function listBlobs(\n\tc: Context<AppEnv>,\n\t_accountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\t// Check if blob storage is configured\n\tif (!c.env.BLOBS) {\n\t\t// No blobs configured, return empty list\n\t\treturn c.json({ cids: [] });\n\t}\n\n\t// List blobs from R2 with prefix\n\tconst prefix = `${did}/`;\n\tconst cursor = c.req.query(\"cursor\");\n\tconst limit = Math.min(Number(c.req.query(\"limit\")) || 500, 1000);\n\n\tconst listed = await c.env.BLOBS.list({\n\t\tprefix,\n\t\tlimit,\n\t\tcursor: cursor || undefined,\n\t});\n\n\t// Extract CIDs from keys (keys are \"${did}/${cid}\")\n\tconst cids = listed.objects.map((obj) => obj.key.slice(prefix.length));\n\n\tconst result: { cids: string[]; cursor?: string } = { cids };\n\tif (listed.truncated && listed.cursor) {\n\t\tresult.cursor = listed.cursor;\n\t}\n\n\treturn c.json(result);\n}\n\nexport async function getBlocks(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\tconst cidsParam = c.req.queries(\"cids\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (!cidsParam || cidsParam.length === 0) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: cids\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst carBytes = await accountDO.rpcGetBlocks(cidsParam);\n\n\treturn new Response(carBytes, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/vnd.ipld.car\",\n\t\t\t\"Content-Length\": carBytes.length.toString(),\n\t\t},\n\t});\n}\n\nexport async function getBlob(\n\tc: Context<AppEnv>,\n\t_accountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\tconst cid = c.req.query(\"cid\");\n\n\tif (!did || !cid) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: did, cid\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\t// Check if blob storage is configured\n\tif (!c.env.BLOBS) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"ServiceUnavailable\",\n\t\t\t\tmessage: \"Blob storage is not configured\",\n\t\t\t},\n\t\t\t503,\n\t\t);\n\t}\n\n\t// Access R2 directly (R2ObjectBody can't be serialized across RPC)\n\tconst key = `${did}/${cid}`;\n\tconst blob = await c.env.BLOBS.get(key);\n\n\tif (!blob) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"BlobNotFound\",\n\t\t\t\tmessage: `Blob not found: ${cid}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\treturn new Response(blob.body, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\":\n\t\t\t\tblob.httpMetadata?.contentType || \"application/octet-stream\",\n\t\t\t\"Content-Length\": blob.size.toString(),\n\t\t},\n\t});\n}\n","","","","","","","","","","","","","","","","","","","","","","","import { Lexicons, jsonToLex, type LexiconDoc } from \"@atproto/lexicon\";\n\n/**\n * Record validator for AT Protocol records.\n *\n * Validates records against official Bluesky lexicon schemas.\n * Uses optimistic validation strategy:\n * - If a lexicon schema is loaded for the collection, validate the record\n * - If no schema is loaded, allow the record (fail-open)\n *\n * This allows the PDS to accept records for new or unknown collection types\n * while still validating known types when schemas are available.\n */\nexport class RecordValidator {\n\tprivate lex: Lexicons;\n\tprivate strictMode: boolean;\n\n\tconstructor(options: { strict?: boolean; lexicons?: Lexicons } = {}) {\n\t\tthis.lex = options.lexicons ?? new Lexicons();\n\t\tthis.strictMode = options.strict ?? false;\n\n\t\t// Load official Bluesky schemas\n\t\tthis.loadBlueskySchemas();\n\t}\n\n\t/**\n\t * Load official Bluesky lexicon schemas from vendored JSON files.\n\t * Uses Vite's glob import to automatically load all schema files.\n\t */\n\tprivate loadBlueskySchemas(): void {\n\t\t// Import all lexicon JSON files using Vite's glob import\n\t\tconst schemas = import.meta.glob<{ default: LexiconDoc }>(\n\t\t\t\"./lexicons/*.json\",\n\t\t\t{ eager: true },\n\t\t);\n\n\t\t// Load each schema into the Lexicons instance\n\t\tfor (const schema of Object.values(schemas)) {\n\t\t\tthis.lex.add(schema.default);\n\t\t}\n\t}\n\n\t/**\n\t * Validate a record against its lexicon schema.\n\t *\n\t * @param collection - The NSID of the record type (e.g., \"app.bsky.feed.post\")\n\t * @param record - The record object to validate\n\t * @throws {Error} If validation fails and schema is loaded\n\t */\n\tvalidateRecord(collection: string, record: unknown): void {\n\t\t// Check if we have a schema for this collection\n\t\tconst hasSchema = this.hasSchema(collection);\n\n\t\tif (!hasSchema) {\n\t\t\t// Optimistic validation: if we don't have the schema, allow it\n\t\t\tif (this.strictMode) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No lexicon schema loaded for collection: ${collection}. Enable optimistic validation or add the schema.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// In non-strict mode, we allow unknown schemas\n\t\t\treturn;\n\t\t}\n\n\t\t// Convert JSON to lexicon format (handles $link -> CID, blob -> BlobRef)\n\t\tconst lexRecord = jsonToLex(record);\n\n\t\t// We have a schema, so validate against it\n\t\ttry {\n\t\t\tthis.lex.assertValidRecord(collection, lexRecord);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new Error(\n\t\t\t\t`Lexicon validation failed for ${collection}: ${message}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Check if a schema is loaded for a collection.\n\t */\n\thasSchema(collection: string): boolean {\n\t\ttry {\n\t\t\t// Try to get the schema - if it exists, this won't throw\n\t\t\tthis.lex.getDefOrThrow(collection);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Add a lexicon schema to the validator.\n\t *\n\t * @param doc - The lexicon document to add\n\t *\n\t * @example\n\t * ```ts\n\t * validator.addSchema({\n\t * lexicon: 1,\n\t * id: \"com.example.post\",\n\t * defs: { ... }\n\t * })\n\t * ```\n\t */\n\taddSchema(doc: any): void {\n\t\tthis.lex.add(doc);\n\t}\n\n\t/**\n\t * Get list of all loaded schema NSIDs.\n\t */\n\tgetLoadedSchemas(): string[] {\n\t\t// Convert the Lexicons iterable to an array and extract IDs\n\t\treturn Array.from(this.lex).map((doc) => doc.id);\n\t}\n\n\t/**\n\t * Get the underlying Lexicons instance for advanced usage.\n\t */\n\tgetLexicons(): Lexicons {\n\t\treturn this.lex;\n\t}\n}\n\n/**\n * Shared validator instance (singleton pattern).\n * Uses optimistic validation by default (strict: false).\n *\n * Automatically loads all schemas from ./lexicons/*.json\n *\n * Additional schemas can be added:\n * ```ts\n * import { validator } from './validation'\n * validator.addSchema(myCustomSchema)\n * ```\n */\nexport const validator = new RecordValidator({ strict: false });\n","import type { Context } from \"hono\";\nimport { AtUri, ensureValidDid } from \"@atproto/syntax\";\nimport { AccountDurableObject } from \"../account-do\";\nimport type { AppEnv, AuthedAppEnv } from \"../types\";\nimport { validator } from \"../validation\";\n\nfunction invalidRecordError(\n\tc: Context<AuthedAppEnv>,\n\terr: unknown,\n\tprefix?: string,\n): Response {\n\tconst message = err instanceof Error ? err.message : String(err);\n\treturn c.json(\n\t\t{\n\t\t\terror: \"InvalidRecord\",\n\t\t\tmessage: prefix ? `${prefix}: ${message}` : message,\n\t\t},\n\t\t400,\n\t);\n}\n\n/**\n * Check if an error is an AccountDeactivated error and return appropriate HTTP 403 response.\n * @param c - Hono context for creating the response\n * @param err - The error to check (expected format: \"AccountDeactivated: <message>\")\n * @returns HTTP 403 Response with AccountDeactivated error type, or null if not a deactivation error\n */\nfunction checkAccountDeactivatedError(\n\tc: Context<AuthedAppEnv>,\n\terr: unknown,\n): Response | null {\n\tconst message = err instanceof Error ? err.message : String(err);\n\tif (message.startsWith(\"AccountDeactivated:\")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AccountDeactivated\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"Account is deactivated. Call activateAccount to enable writes.\",\n\t\t\t},\n\t\t\t403,\n\t\t);\n\t}\n\treturn null;\n}\n\nexport async function describeRepo(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst repo = c.req.query(\"repo\");\n\n\tif (!repo) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: repo\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(repo);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found: ${repo}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst data = await accountDO.rpcDescribeRepo();\n\n\treturn c.json({\n\t\tdid: c.env.DID,\n\t\thandle: c.env.HANDLE,\n\t\tdidDoc: {\n\t\t\t\"@context\": [\"https://www.w3.org/ns/did/v1\"],\n\t\t\tid: c.env.DID,\n\t\t\talsoKnownAs: [`at://${c.env.HANDLE}`],\n\t\t\tverificationMethod: [\n\t\t\t\t{\n\t\t\t\t\tid: `${c.env.DID}#atproto`,\n\t\t\t\t\ttype: \"Multikey\",\n\t\t\t\t\tcontroller: c.env.DID,\n\t\t\t\t\tpublicKeyMultibase: c.env.SIGNING_KEY_PUBLIC,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tcollections: data.collections,\n\t\thandleIsCorrect: true,\n\t});\n}\n\nexport async function getRecord(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst repo = c.req.query(\"repo\");\n\tconst collection = c.req.query(\"collection\");\n\tconst rkey = c.req.query(\"rkey\");\n\n\tif (!repo || !collection || !rkey) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, rkey\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(repo);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found: ${repo}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst result = await accountDO.rpcGetRecord(collection, rkey);\n\n\tif (!result) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RecordNotFound\",\n\t\t\t\tmessage: `Record not found: ${collection}/${rkey}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\treturn c.json({\n\t\turi: AtUri.make(repo, collection, rkey).toString(),\n\t\tcid: result.cid,\n\t\tvalue: result.record,\n\t});\n}\n\nexport async function listRecords(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst repo = c.req.query(\"repo\");\n\tconst collection = c.req.query(\"collection\");\n\tconst limitStr = c.req.query(\"limit\");\n\tconst cursor = c.req.query(\"cursor\");\n\tconst reverseStr = c.req.query(\"reverse\");\n\n\tif (!repo || !collection) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(repo);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found: ${repo}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst limit = Math.min(limitStr ? Number.parseInt(limitStr, 10) : 50, 100);\n\tconst reverse = reverseStr === \"true\";\n\n\tconst result = await accountDO.rpcListRecords(collection, {\n\t\tlimit,\n\t\tcursor,\n\t\treverse,\n\t});\n\n\treturn c.json(result);\n}\n\nexport async function createRecord(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, collection, rkey, record } = body;\n\n\tif (!repo || !collection || !record) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, record\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate record against lexicon schema\n\ttry {\n\t\tvalidator.validateRecord(collection, record);\n\t} catch (err) {\n\t\treturn invalidRecordError(c, err);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcCreateRecord(collection, rkey, record);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\tthrow err;\n\t}\n}\n\nexport async function deleteRecord(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, collection, rkey } = body;\n\n\tif (!repo || !collection || !rkey) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, rkey\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcDeleteRecord(collection, rkey);\n\n\t\tif (!result) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"RecordNotFound\",\n\t\t\t\t\tmessage: `Record not found: ${collection}/${rkey}`,\n\t\t\t\t},\n\t\t\t\t404,\n\t\t\t);\n\t\t}\n\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\tthrow err;\n\t}\n}\n\nexport async function putRecord(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, collection, rkey, record } = body;\n\n\tif (!repo || !collection || !rkey || !record) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, rkey, record\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate record against lexicon schema\n\ttry {\n\t\tvalidator.validateRecord(collection, record);\n\t} catch (err) {\n\t\treturn invalidRecordError(c, err);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcPutRecord(collection, rkey, record);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n}\n\nexport async function applyWrites(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, writes } = body;\n\n\tif (!repo || !writes || !Array.isArray(writes)) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, writes\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (writes.length > 200) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Too many writes. Max: 200\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate all records in create and update operations\n\tfor (let i = 0; i < writes.length; i++) {\n\t\tconst write = writes[i];\n\t\tif (\n\t\t\twrite.$type === \"com.atproto.repo.applyWrites#create\" ||\n\t\t\twrite.$type === \"com.atproto.repo.applyWrites#update\"\n\t\t) {\n\t\t\ttry {\n\t\t\t\tvalidator.validateRecord(write.collection, write.value);\n\t\t\t} catch (err) {\n\t\t\t\treturn invalidRecordError(c, err, `Write ${i}`);\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcApplyWrites(writes);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n}\n\nexport async function uploadBlob(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst contentType =\n\t\tc.req.header(\"Content-Type\") || \"application/octet-stream\";\n\tconst bytes = new Uint8Array(await c.req.arrayBuffer());\n\n\t// Size limit check (5MB)\n\tconst MAX_BLOB_SIZE = 5 * 1024 * 1024;\n\tif (bytes.length > MAX_BLOB_SIZE) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"BlobTooLarge\",\n\t\t\t\tmessage: `Blob size ${bytes.length} exceeds maximum of ${MAX_BLOB_SIZE} bytes`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\ttry {\n\t\tconst blobRef = await accountDO.rpcUploadBlob(bytes, contentType);\n\t\treturn c.json({ blob: blobRef });\n\t} catch (err) {\n\t\tif (\n\t\t\terr instanceof Error &&\n\t\t\terr.message.includes(\"Blob storage not configured\")\n\t\t) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ServiceUnavailable\",\n\t\t\t\t\tmessage: \"Blob storage is not configured\",\n\t\t\t\t},\n\t\t\t\t503,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nexport async function importRepo(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst contentType = c.req.header(\"Content-Type\");\n\n\t// Verify content type\n\tif (contentType !== \"application/vnd.ipld.car\") {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"Content-Type must be application/vnd.ipld.car for repository import\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Get CAR file bytes\n\tconst carBytes = new Uint8Array(await c.req.arrayBuffer());\n\n\tif (carBytes.length === 0) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Empty CAR file\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Size limit check (100MB for repo imports)\n\tconst MAX_CAR_SIZE = 100 * 1024 * 1024;\n\tif (carBytes.length > MAX_CAR_SIZE) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoTooLarge\",\n\t\t\t\tmessage: `Repository size ${carBytes.length} exceeds maximum of ${MAX_CAR_SIZE} bytes`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcImportRepo(carBytes);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tif (err instanceof Error) {\n\t\t\tif (err.message.includes(\"already exists\")) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"RepoAlreadyExists\",\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"Repository already exists. Cannot import over existing data.\",\n\t\t\t\t\t},\n\t\t\t\t\t409,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (err.message.includes(\"DID mismatch\")) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\t\t\tmessage: err.message,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (\n\t\t\t\terr.message.includes(\"no roots\") ||\n\t\t\t\terr.message.includes(\"no blocks\") ||\n\t\t\t\terr.message.includes(\"Invalid root\")\n\t\t\t) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\t\t\tmessage: `Invalid CAR file: ${err.message}`,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/**\n * List blobs that are referenced in records but not yet imported.\n * Used during migration to track which blobs still need to be uploaded.\n */\nexport async function listMissingBlobs(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst limitStr = c.req.query(\"limit\");\n\tconst cursor = c.req.query(\"cursor\");\n\n\tconst limit = limitStr ? Math.min(Number.parseInt(limitStr, 10), 500) : 500;\n\n\tconst result = await accountDO.rpcListMissingBlobs(\n\t\tlimit,\n\t\tcursor || undefined,\n\t);\n\n\treturn c.json(result);\n}\n","import type { Context } from \"hono\";\nimport type { AccountDurableObject } from \"../account-do\";\nimport { createServiceJwt, getSigningKeypair } from \"../service-auth\";\nimport {\n\tcreateAccessToken,\n\tcreateRefreshToken,\n\tverifyPassword,\n\tverifyAccessToken,\n\tverifyRefreshToken,\n} from \"../session\";\nimport type { AppEnv, AuthedAppEnv } from \"../types\";\n\nexport async function describeServer(c: Context<AppEnv>): Promise<Response> {\n\treturn c.json({\n\t\tdid: c.env.DID,\n\t\tavailableUserDomains: [],\n\t\tinviteCodeRequired: false,\n\t});\n}\n\n/**\n * Create a new session (login)\n */\nexport async function createSession(c: Context<AppEnv>): Promise<Response> {\n\tconst body = await c.req.json<{\n\t\tidentifier: string;\n\t\tpassword: string;\n\t}>();\n\n\tconst { identifier, password } = body;\n\n\tif (!identifier || !password) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing identifier or password\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Check identifier matches handle or DID\n\tif (identifier !== c.env.HANDLE && identifier !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Invalid identifier or password\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\t// Verify password\n\tconst passwordValid = await verifyPassword(password, c.env.PASSWORD_HASH);\n\tif (!passwordValid) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Invalid identifier or password\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\t// Create tokens\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\tconst accessJwt = await createAccessToken(\n\t\tc.env.JWT_SECRET,\n\t\tc.env.DID,\n\t\tserviceDid,\n\t);\n\tconst refreshJwt = await createRefreshToken(\n\t\tc.env.JWT_SECRET,\n\t\tc.env.DID,\n\t\tserviceDid,\n\t);\n\n\treturn c.json({\n\t\taccessJwt,\n\t\trefreshJwt,\n\t\thandle: c.env.HANDLE,\n\t\tdid: c.env.DID,\n\t\tactive: true,\n\t});\n}\n\n/**\n * Refresh a session\n */\nexport async function refreshSession(c: Context<AppEnv>): Promise<Response> {\n\tconst authHeader = c.req.header(\"Authorization\");\n\n\tif (!authHeader?.startsWith(\"Bearer \")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Refresh token required\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\tconst token = authHeader.slice(7);\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\ttry {\n\t\tconst payload = await verifyRefreshToken(\n\t\t\ttoken,\n\t\t\tc.env.JWT_SECRET,\n\t\t\tserviceDid,\n\t\t);\n\n\t\t// Verify the subject matches our DID\n\t\tif (payload.sub !== c.env.DID) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid refresh token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\t// Create new tokens\n\t\tconst accessJwt = await createAccessToken(\n\t\t\tc.env.JWT_SECRET,\n\t\t\tc.env.DID,\n\t\t\tserviceDid,\n\t\t);\n\t\tconst refreshJwt = await createRefreshToken(\n\t\t\tc.env.JWT_SECRET,\n\t\t\tc.env.DID,\n\t\t\tserviceDid,\n\t\t);\n\n\t\treturn c.json({\n\t\t\taccessJwt,\n\t\t\trefreshJwt,\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Invalid refresh token\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n}\n\n/**\n * Get current session info\n */\nexport async function getSession(c: Context<AppEnv>): Promise<Response> {\n\tconst authHeader = c.req.header(\"Authorization\");\n\n\tif (!authHeader?.startsWith(\"Bearer \")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Access token required\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\tconst token = authHeader.slice(7);\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\t// First try static token\n\tif (token === c.env.AUTH_TOKEN) {\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\tactive: true,\n\t\t});\n\t}\n\n\t// Try JWT\n\ttry {\n\t\tconst payload = await verifyAccessToken(\n\t\t\ttoken,\n\t\t\tc.env.JWT_SECRET,\n\t\t\tserviceDid,\n\t\t);\n\n\t\tif (payload.sub !== c.env.DID) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid access token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidToken\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Invalid access token\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n}\n\n/**\n * Delete current session (logout)\n */\nexport async function deleteSession(c: Context<AppEnv>): Promise<Response> {\n\t// For a single-user PDS with stateless JWTs, we don't need to do anything\n\t// The client just needs to delete its stored tokens\n\t// In a full implementation, we'd revoke the refresh token\n\treturn c.json({});\n}\n\n/**\n * Get account status - used for migration checks and progress tracking\n */\nexport async function getAccountStatus(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\t// Check if repo exists and get activation state\n\t\tconst status = await accountDO.rpcGetRepoStatus();\n\t\tconst active = await accountDO.rpcGetActive();\n\n\t\t// Get counts for migration progress tracking\n\t\tconst [repoBlocks, indexedRecords, expectedBlobs, importedBlobs] =\n\t\t\tawait Promise.all([\n\t\t\t\taccountDO.rpcCountBlocks(),\n\t\t\t\taccountDO.rpcCountRecords(),\n\t\t\t\taccountDO.rpcCountExpectedBlobs(),\n\t\t\t\taccountDO.rpcCountImportedBlobs(),\n\t\t\t]);\n\n\t\treturn c.json({\n\t\t\tactive: active,\n\t\t\tvalidDid: true,\n\t\t\trepoCommit: status.head,\n\t\t\trepoRev: status.rev,\n\t\t\trepoBlocks,\n\t\t\tindexedRecords,\n\t\t\tprivateStateValues: null,\n\t\t\texpectedBlobs,\n\t\t\timportedBlobs,\n\t\t});\n\t} catch (err) {\n\t\t// If repo doesn't exist yet, return empty status\n\t\treturn c.json({\n\t\t\tactive: false,\n\t\t\tvalidDid: true,\n\t\t\trepoCommit: null,\n\t\t\trepoRev: null,\n\t\t\trepoBlocks: 0,\n\t\t\tindexedRecords: 0,\n\t\t\tprivateStateValues: null,\n\t\t\texpectedBlobs: 0,\n\t\t\timportedBlobs: 0,\n\t\t});\n\t}\n}\n\n/**\n * Get a service auth token for communicating with external services.\n * Used by clients to get JWTs for services like video.bsky.app.\n */\nexport async function getServiceAuth(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\tconst aud = c.req.query(\"aud\");\n\tconst lxm = c.req.query(\"lxm\") || null;\n\n\tif (!aud) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: aud\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Create service JWT for the requested audience\n\tconst keypair = await getSigningKeypair(c.env.SIGNING_KEY);\n\tconst token = await createServiceJwt({\n\t\tiss: c.env.DID,\n\t\taud,\n\t\tlxm,\n\t\tkeypair,\n\t});\n\n\treturn c.json({ token });\n}\n\n/**\n * Activate account - enables writes and firehose events\n */\nexport async function activateAccount(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\tawait accountDO.rpcActivateAccount();\n\t\treturn c.json({ success: true });\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Unknown error\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n\n/**\n * Deactivate account - disables writes while keeping reads available\n */\nexport async function deactivateAccount(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\tawait accountDO.rpcDeactivateAccount();\n\t\treturn c.json({ success: true });\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Unknown error\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n\n/**\n * Reset migration state - clears imported repo and blob tracking.\n * Only works on deactivated accounts.\n */\nexport async function resetMigration(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\tconst result = await accountDO.rpcResetMigration();\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"Unknown error\";\n\n\t\t// Check for specific error types\n\t\tif (message.includes(\"AccountActive\")) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AccountActive\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Cannot reset migration on an active account. Deactivate first.\",\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage,\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n","","// Public API\nexport { AccountDurableObject } from \"./account-do\";\nexport type { PDSEnv } from \"./types\";\n\nimport { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { env as _env } from \"cloudflare:workers\";\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { ensureValidDid, ensureValidHandle } from \"@atproto/syntax\";\nimport { requireAuth } from \"./middleware/auth\";\nimport { DidResolver } from \"./did-resolver\";\nimport { WorkersDidCache } from \"./did-cache\";\nimport { handleXrpcProxy } from \"./xrpc-proxy\";\nimport { createOAuthApp } from \"./oauth\";\nimport * as sync from \"./xrpc/sync\";\nimport * as repo from \"./xrpc/repo\";\nimport * as server from \"./xrpc/server\";\nimport type { PDSEnv } from \"./types\";\n\nimport { version } from \"../package.json\" with { type: \"json\" };\n\n// Cast env to PDSEnv for type safety\nconst env = _env as PDSEnv;\n\n// Validate required environment variables at module load\nconst required = [\n\t\"DID\",\n\t\"HANDLE\",\n\t\"PDS_HOSTNAME\",\n\t\"AUTH_TOKEN\",\n\t\"SIGNING_KEY\",\n\t\"SIGNING_KEY_PUBLIC\",\n\t\"JWT_SECRET\",\n\t\"PASSWORD_HASH\",\n] as const;\n\nfor (const key of required) {\n\tif (!env[key]) {\n\t\tthrow new Error(`Missing required environment variable: ${key}`);\n\t}\n}\n\n// Validate DID and handle formats\ntry {\n\tensureValidDid(env.DID);\n\tensureValidHandle(env.HANDLE);\n} catch (err) {\n\tthrow new Error(\n\t\t`Invalid DID or handle: ${err instanceof Error ? err.message : String(err)}`,\n\t);\n}\n\nconst didResolver = new DidResolver({\n\tdidCache: new WorkersDidCache(),\n\ttimeout: 3000, // 3 second timeout for DID resolution\n\tplcUrl: \"https://plc.directory\",\n});\n\n// Lazy-loaded keypair for service auth\nlet keypairPromise: Promise<Secp256k1Keypair> | null = null;\nfunction getKeypair(): Promise<Secp256k1Keypair> {\n\tif (!keypairPromise) {\n\t\tkeypairPromise = Secp256k1Keypair.import(env.SIGNING_KEY);\n\t}\n\treturn keypairPromise;\n}\n\nconst app = new Hono<{ Bindings: PDSEnv }>();\n\n// CORS middleware for all routes\napp.use(\n\t\"*\",\n\tcors({\n\t\torigin: \"*\",\n\t\tallowMethods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n\t\tallowHeaders: [\"*\"],\n\t\texposeHeaders: [\"Content-Type\"],\n\t\tmaxAge: 86400,\n\t}),\n);\n\n// Helper to get Account DO stub\nfunction getAccountDO(env: PDSEnv) {\n\tconst id = env.ACCOUNT.idFromName(\"account\");\n\treturn env.ACCOUNT.get(id);\n}\n\n// DID document for did:web resolution\napp.get(\"/.well-known/did.json\", (c) => {\n\tconst didDocument = {\n\t\t\"@context\": [\n\t\t\t\"https://www.w3.org/ns/did/v1\",\n\t\t\t\"https://w3id.org/security/multikey/v1\",\n\t\t\t\"https://w3id.org/security/suites/secp256k1-2019/v1\",\n\t\t],\n\t\tid: c.env.DID,\n\t\talsoKnownAs: [`at://${c.env.HANDLE}`],\n\t\tverificationMethod: [\n\t\t\t{\n\t\t\t\tid: `${c.env.DID}#atproto`,\n\t\t\t\ttype: \"Multikey\",\n\t\t\t\tcontroller: c.env.DID,\n\t\t\t\tpublicKeyMultibase: c.env.SIGNING_KEY_PUBLIC,\n\t\t\t},\n\t\t],\n\t\tservice: [\n\t\t\t{\n\t\t\t\tid: \"#atproto_pds\",\n\t\t\t\ttype: \"AtprotoPersonalDataServer\",\n\t\t\t\tserviceEndpoint: `https://${c.env.PDS_HOSTNAME}`,\n\t\t\t},\n\t\t],\n\t};\n\treturn c.json(didDocument);\n});\n\n// Handle verification for AT Protocol\n// Only served if handle matches PDS hostname\napp.get(\"/.well-known/atproto-did\", (c) => {\n\tif (c.env.HANDLE !== c.env.PDS_HOSTNAME) {\n\t\treturn c.notFound();\n\t}\n\treturn new Response(c.env.DID, {\n\t\theaders: { \"Content-Type\": \"text/plain\" },\n\t});\n});\n\n// Health check\napp.get(\"/health\", (c) =>\n\tc.json({\n\t\tstatus: \"ok\",\n\t\tversion,\n\t}),\n);\n\n// Sync endpoints (federation)\napp.get(\"/xrpc/com.atproto.sync.getRepo\", (c) =>\n\tsync.getRepo(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.getRepoStatus\", (c) =>\n\tsync.getRepoStatus(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.getBlocks\", (c) =>\n\tsync.getBlocks(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.getBlob\", (c) =>\n\tsync.getBlob(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.listRepos\", (c) =>\n\tsync.listRepos(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.listBlobs\", (c) =>\n\tsync.listBlobs(c, getAccountDO(c.env)),\n);\n\n// WebSocket firehose\napp.get(\"/xrpc/com.atproto.sync.subscribeRepos\", async (c) => {\n\tconst upgradeHeader = c.req.header(\"Upgrade\");\n\tif (upgradeHeader !== \"websocket\") {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Expected WebSocket upgrade\" },\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Use fetch() instead of RPC to avoid WebSocket serialization error\n\tconst accountDO = getAccountDO(c.env);\n\treturn accountDO.fetch(c.req.raw);\n});\n\n// Repository operations\napp.get(\"/xrpc/com.atproto.repo.describeRepo\", (c) =>\n\trepo.describeRepo(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.repo.getRecord\", (c) =>\n\trepo.getRecord(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.repo.listRecords\", (c) =>\n\trepo.listRecords(c, getAccountDO(c.env)),\n);\n\n// Write operations require authentication\napp.post(\"/xrpc/com.atproto.repo.createRecord\", requireAuth, (c) =>\n\trepo.createRecord(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.deleteRecord\", requireAuth, (c) =>\n\trepo.deleteRecord(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.uploadBlob\", requireAuth, (c) =>\n\trepo.uploadBlob(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.applyWrites\", requireAuth, (c) =>\n\trepo.applyWrites(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.putRecord\", requireAuth, (c) =>\n\trepo.putRecord(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.importRepo\", requireAuth, (c) =>\n\trepo.importRepo(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.repo.listMissingBlobs\", requireAuth, (c) =>\n\trepo.listMissingBlobs(c, getAccountDO(c.env)),\n);\n\n// Server identity\napp.get(\"/xrpc/com.atproto.server.describeServer\", server.describeServer);\n\n// Handle resolution - return our DID for our handle, let others fall through to proxy\napp.use(\"/xrpc/com.atproto.identity.resolveHandle\", async (c, next) => {\n\tconst handle = c.req.query(\"handle\");\n\tif (handle === c.env.HANDLE) {\n\t\treturn c.json({ did: c.env.DID });\n\t}\n\tawait next();\n});\n\n// Session management\napp.post(\"/xrpc/com.atproto.server.createSession\", server.createSession);\napp.post(\"/xrpc/com.atproto.server.refreshSession\", server.refreshSession);\napp.get(\"/xrpc/com.atproto.server.getSession\", server.getSession);\napp.post(\"/xrpc/com.atproto.server.deleteSession\", server.deleteSession);\n\n// Account lifecycle\napp.get(\"/xrpc/com.atproto.server.getAccountStatus\", requireAuth, (c) =>\n\tserver.getAccountStatus(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.activateAccount\", requireAuth, (c) =>\n\tserver.activateAccount(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.deactivateAccount\", requireAuth, (c) =>\n\tserver.deactivateAccount(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/gg.mk.experimental.resetMigration\", requireAuth, (c) =>\n\tserver.resetMigration(c, getAccountDO(c.env)),\n);\n\n// Service auth - used by clients to get JWTs for external services (video, etc.)\napp.get(\n\t\"/xrpc/com.atproto.server.getServiceAuth\",\n\trequireAuth,\n\tserver.getServiceAuth,\n);\n\n// Actor preferences\napp.get(\"/xrpc/app.bsky.actor.getPreferences\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst result = await accountDO.rpcGetPreferences();\n\treturn c.json(result);\n});\napp.post(\"/xrpc/app.bsky.actor.putPreferences\", requireAuth, async (c) => {\n\tconst body = await c.req.json<{ preferences: unknown[] }>();\n\tconst accountDO = getAccountDO(c.env);\n\tawait accountDO.rpcPutPreferences(body.preferences);\n\treturn c.json({});\n});\n\n// Age assurance (stub - self-hosted users are pre-verified)\napp.get(\"/xrpc/app.bsky.ageassurance.getState\", requireAuth, (c) => {\n\treturn c.json({\n\t\tstate: {\n\t\t\tstatus: \"assured\",\n\t\t\taccess: \"full\",\n\t\t\tlastInitiatedAt: new Date().toISOString(),\n\t\t},\n\t\tmetadata: {\n\t\t\taccountCreatedAt: new Date().toISOString(),\n\t\t},\n\t});\n});\n\n// Admin: Emit identity event to refresh handle verification\napp.post(\"/admin/emit-identity\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst result = await accountDO.rpcEmitIdentityEvent(c.env.HANDLE);\n\treturn c.json(result);\n});\n\n// OAuth 2.1 endpoints for \"Login with Bluesky\"\nconst oauthApp = createOAuthApp(getAccountDO);\napp.route(\"/\", oauthApp);\n\n// Proxy unhandled XRPC requests to services specified via atproto-proxy header\n// or fall back to Bluesky services for backward compatibility\napp.all(\"/xrpc/*\", (c) => handleXrpcProxy(c, didResolver, getKeypair));\n\nexport default app;\n"],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
1
+ {"version":3,"file":"index.js","names":["sql: SqlStorage","missing: CID[]","blobs: Array<{ cid: string; recordUri: string }>","sql: SqlStorage","sql: SqlStorage","eventPayload: Omit<CommitEvent, \"seq\">","cborEncode","cborDecode","r2: R2Bucket","did: string","env","collections: string[]","createOp: RecordCreateOp","commitData: CommitData","deleteOp: RecordDeleteOp","op: RecordWriteOp","ops: RecordWriteOp[]","results: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t\tcollection: string;\n\t\t\trkey: string;\n\t\t\taction: WriteOpAction;\n\t\t}>","op: RecordCreateOp","op: RecordUpdateOp","op: RecordDeleteOp","finalResults: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t}>","opsWithCids: Array<RecordWriteOp & { cid?: CID | null }>","cborEncode","result: Record<string, unknown>","cids: string[]","cachedKeypair: Secp256k1Keypair | null","cachedSigningKey: string | null","result: Partial<T>","payload: ServiceJwtPayload","accountDO: DurableObjectStub<AccountDurableObject>","getAccountDO","env","getAccountDO: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>","token: string | undefined","audienceDid: string","targetUrl: URL","didResolver","headers: Record<string, string>","userDid: string | undefined","getKeypair","reqInit: RequestInit","result: { cids: string[]; cursor?: string }","verifyPassword","env","_env","keypairPromise: Promise<Secp256k1Keypair> | null","id","didDocument","sync.getRepo","sync.getRepoStatus","sync.getBlocks","sync.getBlob","sync.listRepos","sync.listBlobs","repo.describeRepo","repo.getRecord","repo.listRecords","repo.createRecord","repo.deleteRecord","repo.uploadBlob","repo.applyWrites","repo.putRecord","repo.importRepo","repo.listMissingBlobs","server.describeServer","server.createSession","server.refreshSession","server.getSession","server.deleteSession","server.getAccountStatus","server.activateAccount","server.deactivateAccount","server.resetMigration","server.getServiceAuth"],"sources":["../src/storage.ts","../src/oauth-storage.ts","../src/sequencer.ts","../src/blobs.ts","../src/account-do.ts","../src/service-auth.ts","../src/session.ts","../src/oauth.ts","../src/middleware/auth.ts","../src/did-resolver.ts","../src/did-cache.ts","../src/xrpc-proxy.ts","../src/xrpc/sync.ts","../src/lexicons/app.bsky.actor.defs.json","../src/lexicons/app.bsky.actor.profile.json","../src/lexicons/app.bsky.embed.defs.json","../src/lexicons/app.bsky.embed.external.json","../src/lexicons/app.bsky.embed.images.json","../src/lexicons/app.bsky.embed.record.json","../src/lexicons/app.bsky.embed.recordWithMedia.json","../src/lexicons/app.bsky.embed.video.json","../src/lexicons/app.bsky.feed.defs.json","../src/lexicons/app.bsky.feed.like.json","../src/lexicons/app.bsky.feed.post.json","../src/lexicons/app.bsky.feed.repost.json","../src/lexicons/app.bsky.feed.threadgate.json","../src/lexicons/app.bsky.graph.block.json","../src/lexicons/app.bsky.graph.defs.json","../src/lexicons/app.bsky.graph.follow.json","../src/lexicons/app.bsky.graph.list.json","../src/lexicons/app.bsky.graph.listitem.json","../src/lexicons/app.bsky.notification.defs.json","../src/lexicons/app.bsky.richtext.facet.json","../src/lexicons/com.atproto.label.defs.json","../src/lexicons/com.atproto.repo.strongRef.json","../src/validation.ts","../src/xrpc/repo.ts","../src/xrpc/server.ts","../package.json","../src/index.ts"],"sourcesContent":["import { CID } from \"@atproto/lex-data\";\nimport { BlockMap, type CommitData } from \"@atproto/repo\";\nimport { ReadableBlockstore, type RepoStorage } from \"@atproto/repo\";\n\n/**\n * SQLite-backed repository storage for Cloudflare Durable Objects.\n *\n * Implements the RepoStorage interface from @atproto/repo, storing blocks\n * in a SQLite database within a Durable Object.\n */\nexport class SqliteRepoStorage\n\textends ReadableBlockstore\n\timplements RepoStorage\n{\n\tconstructor(private sql: SqlStorage) {\n\t\tsuper();\n\t}\n\n\t/**\n\t * Initialize the database schema. Should be called once on DO startup.\n\t * @param initialActive - Whether the account should start in active state (default true)\n\t */\n\tinitSchema(initialActive: boolean = true): void {\n\t\tthis.sql.exec(`\n\t\t\t-- Block storage (MST nodes + record blocks)\n\t\t\tCREATE TABLE IF NOT EXISTS blocks (\n\t\t\t\tcid TEXT PRIMARY KEY,\n\t\t\t\tbytes BLOB NOT NULL,\n\t\t\t\trev TEXT NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_blocks_rev ON blocks(rev);\n\n\t\t\t-- Repo state (single row)\n\t\t\tCREATE TABLE IF NOT EXISTS repo_state (\n\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\troot_cid TEXT,\n\t\t\t\trev TEXT,\n\t\t\t\tseq INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tactive INTEGER NOT NULL DEFAULT 1\n\t\t\t);\n\n\t\t\t-- Initialize with empty state if not exists\n\t\t\tINSERT OR IGNORE INTO repo_state (id, root_cid, rev, seq, active)\n\t\t\tVALUES (1, NULL, NULL, 0, ${initialActive ? 1 : 0});\n\n\t\t\t-- Firehose events (sequenced commit log)\n\t\t\tCREATE TABLE IF NOT EXISTS firehose_events (\n\t\t\t\tseq INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tevent_type TEXT NOT NULL,\n\t\t\t\tpayload BLOB NOT NULL,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_firehose_created_at ON firehose_events(created_at);\n\n\t\t\t-- User preferences (single row, stores JSON array)\n\t\t\tCREATE TABLE IF NOT EXISTS preferences (\n\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\tdata TEXT NOT NULL DEFAULT '[]'\n\t\t\t);\n\n\t\t\t-- Initialize with empty preferences array if not exists\n\t\t\tINSERT OR IGNORE INTO preferences (id, data) VALUES (1, '[]');\n\n\t\t\t-- Track blob references in records (populated during importRepo)\n\t\t\tCREATE TABLE IF NOT EXISTS record_blob (\n\t\t\t\trecordUri TEXT NOT NULL,\n\t\t\t\tblobCid TEXT NOT NULL,\n\t\t\t\tPRIMARY KEY (recordUri, blobCid)\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_record_blob_cid ON record_blob(blobCid);\n\n\t\t\t-- Track successfully imported blobs (populated during uploadBlob)\n\t\t\tCREATE TABLE IF NOT EXISTS imported_blobs (\n\t\t\t\tcid TEXT PRIMARY KEY,\n\t\t\t\tsize INTEGER NOT NULL,\n\t\t\t\tmimeType TEXT,\n\t\t\t\tcreatedAt TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t);\n\t\t`);\n\t}\n\n\t/**\n\t * Get the current root CID of the repository.\n\t */\n\tasync getRoot(): Promise<CID | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT root_cid FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\tif (rows.length === 0 || !rows[0]?.root_cid) {\n\t\t\treturn null;\n\t\t}\n\t\treturn CID.parse(rows[0]!.root_cid as string);\n\t}\n\n\t/**\n\t * Get the current revision string.\n\t */\n\tasync getRev(): Promise<string | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT rev FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.rev as string) ?? null) : null;\n\t}\n\n\t/**\n\t * Get the current sequence number for firehose events.\n\t */\n\tasync getSeq(): Promise<number> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT seq FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.seq as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Increment and return the next sequence number.\n\t */\n\tasync nextSeq(): Promise<number> {\n\t\tthis.sql.exec(\"UPDATE repo_state SET seq = seq + 1 WHERE id = 1\");\n\t\treturn this.getSeq();\n\t}\n\n\t/**\n\t * Get the raw bytes for a block by CID.\n\t */\n\tasync getBytes(cid: CID): Promise<Uint8Array | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT bytes FROM blocks WHERE cid = ?\", cid.toString())\n\t\t\t.toArray();\n\t\tif (rows.length === 0 || !rows[0]?.bytes) {\n\t\t\treturn null;\n\t\t}\n\t\t// SQLite returns ArrayBuffer, convert to Uint8Array\n\t\treturn new Uint8Array(rows[0]!.bytes as ArrayBuffer);\n\t}\n\n\t/**\n\t * Check if a block exists.\n\t */\n\tasync has(cid: CID): Promise<boolean> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT 1 FROM blocks WHERE cid = ? LIMIT 1\", cid.toString())\n\t\t\t.toArray();\n\t\treturn rows.length > 0;\n\t}\n\n\t/**\n\t * Get multiple blocks at once.\n\t */\n\tasync getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }> {\n\t\tconst blocks = new BlockMap();\n\t\tconst missing: CID[] = [];\n\n\t\tfor (const cid of cids) {\n\t\t\tconst bytes = await this.getBytes(cid);\n\t\t\tif (bytes) {\n\t\t\t\tblocks.set(cid, bytes);\n\t\t\t} else {\n\t\t\t\tmissing.push(cid);\n\t\t\t}\n\t\t}\n\n\t\treturn { blocks, missing };\n\t}\n\n\t/**\n\t * Store a single block.\n\t */\n\tasync putBlock(cid: CID, block: Uint8Array, rev: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR REPLACE INTO blocks (cid, bytes, rev) VALUES (?, ?, ?)\",\n\t\t\tcid.toString(),\n\t\t\tblock,\n\t\t\trev,\n\t\t);\n\t}\n\n\t/**\n\t * Store multiple blocks at once.\n\t */\n\tasync putMany(blocks: BlockMap, rev: string): Promise<void> {\n\t\t// Access BlockMap's internal map to avoid iterator issues in Workers environment\n\t\t// BlockMap stores data in a Map<string, Uint8Array> internally as 'map' (private field)\n\t\tconst internalMap = (blocks as unknown as { map: Map<string, Uint8Array> })\n\t\t\t.map;\n\t\tif (internalMap) {\n\t\t\tfor (const [cidStr, bytes] of internalMap) {\n\t\t\t\tthis.sql.exec(\n\t\t\t\t\t\"INSERT OR REPLACE INTO blocks (cid, bytes, rev) VALUES (?, ?, ?)\",\n\t\t\t\t\tcidStr,\n\t\t\t\t\tbytes,\n\t\t\t\t\trev,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update the repository root.\n\t */\n\tasync updateRoot(cid: CID, rev: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE repo_state SET root_cid = ?, rev = ? WHERE id = 1\",\n\t\t\tcid.toString(),\n\t\t\trev,\n\t\t);\n\t}\n\n\t/**\n\t * Apply a commit atomically: add new blocks, remove old blocks, update root.\n\t */\n\tasync applyCommit(commit: CommitData): Promise<void> {\n\t\t// Note: Durable Object SQLite doesn't support BEGIN/COMMIT,\n\t\t// but operations within a single DO request are already atomic.\n\n\t\t// Access BlockMap's internal map to avoid iterator issues in Workers environment\n\t\tconst internalMap = (\n\t\t\tcommit.newBlocks as unknown as { map: Map<string, Uint8Array> }\n\t\t).map;\n\t\tif (internalMap) {\n\t\t\tfor (const [cidStr, bytes] of internalMap) {\n\t\t\t\tthis.sql.exec(\n\t\t\t\t\t\"INSERT OR REPLACE INTO blocks (cid, bytes, rev) VALUES (?, ?, ?)\",\n\t\t\t\t\tcidStr,\n\t\t\t\t\tbytes,\n\t\t\t\t\tcommit.rev,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Remove old blocks - access CidSet's internal set to avoid CID.parse shim issues\n\t\tconst removedSet = (commit.removedCids as unknown as { set: Set<string> })\n\t\t\t.set;\n\t\tif (removedSet) {\n\t\t\tfor (const cidStr of removedSet) {\n\t\t\t\tthis.sql.exec(\"DELETE FROM blocks WHERE cid = ?\", cidStr);\n\t\t\t}\n\t\t}\n\n\t\t// Update root\n\t\tawait this.updateRoot(commit.cid, commit.rev);\n\t}\n\n\t/**\n\t * Get total storage size in bytes.\n\t */\n\tasync sizeInBytes(): Promise<number> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT SUM(LENGTH(bytes)) as total FROM blocks\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.total as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Clear all data (for testing).\n\t */\n\tasync destroy(): Promise<void> {\n\t\tthis.sql.exec(\"DELETE FROM blocks\");\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE repo_state SET root_cid = NULL, rev = NULL WHERE id = 1\",\n\t\t);\n\t}\n\n\t/**\n\t * Count the number of blocks stored.\n\t */\n\tasync countBlocks(): Promise<number> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT COUNT(*) as count FROM blocks\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.count as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Get user preferences.\n\t */\n\tasync getPreferences(): Promise<unknown[]> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT data FROM preferences WHERE id = 1\")\n\t\t\t.toArray();\n\t\tif (rows.length === 0 || !rows[0]?.data) {\n\t\t\treturn [];\n\t\t}\n\t\tconst data = rows[0]!.data as string;\n\t\ttry {\n\t\t\treturn JSON.parse(data);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Update user preferences.\n\t */\n\tasync putPreferences(preferences: unknown[]): Promise<void> {\n\t\tconst data = JSON.stringify(preferences);\n\t\tthis.sql.exec(\"UPDATE preferences SET data = ? WHERE id = 1\", data);\n\t}\n\n\t/**\n\t * Get the activation state of the account.\n\t */\n\tasync getActive(): Promise<boolean> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT active FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.active as number) === 1) : true;\n\t}\n\n\t/**\n\t * Set the activation state of the account.\n\t */\n\tasync setActive(active: boolean): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE repo_state SET active = ? WHERE id = 1\",\n\t\t\tactive ? 1 : 0,\n\t\t);\n\t}\n\n\t// ============================================\n\t// Blob Tracking Methods\n\t// ============================================\n\n\t/**\n\t * Add a blob reference from a record.\n\t */\n\taddRecordBlob(recordUri: string, blobCid: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR IGNORE INTO record_blob (recordUri, blobCid) VALUES (?, ?)\",\n\t\t\trecordUri,\n\t\t\tblobCid,\n\t\t);\n\t}\n\n\t/**\n\t * Add multiple blob references from a record.\n\t */\n\taddRecordBlobs(recordUri: string, blobCids: string[]): void {\n\t\tfor (const cid of blobCids) {\n\t\t\tthis.addRecordBlob(recordUri, cid);\n\t\t}\n\t}\n\n\t/**\n\t * Remove all blob references for a record.\n\t */\n\tremoveRecordBlobs(recordUri: string): void {\n\t\tthis.sql.exec(\"DELETE FROM record_blob WHERE recordUri = ?\", recordUri);\n\t}\n\n\t/**\n\t * Track an imported blob.\n\t */\n\ttrackImportedBlob(cid: string, size: number, mimeType: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR REPLACE INTO imported_blobs (cid, size, mimeType) VALUES (?, ?, ?)\",\n\t\t\tcid,\n\t\t\tsize,\n\t\t\tmimeType,\n\t\t);\n\t}\n\n\t/**\n\t * Check if a blob has been imported.\n\t */\n\tisBlobImported(cid: string): boolean {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT 1 FROM imported_blobs WHERE cid = ? LIMIT 1\", cid)\n\t\t\t.toArray();\n\t\treturn rows.length > 0;\n\t}\n\n\t/**\n\t * Count expected blobs (distinct blobs referenced by records).\n\t */\n\tcountExpectedBlobs(): number {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT COUNT(DISTINCT blobCid) as count FROM record_blob\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.count as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * Count imported blobs.\n\t */\n\tcountImportedBlobs(): number {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT COUNT(*) as count FROM imported_blobs\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.count as number) ?? 0) : 0;\n\t}\n\n\t/**\n\t * List blobs that are referenced but not yet imported.\n\t */\n\tlistMissingBlobs(\n\t\tlimit: number = 500,\n\t\tcursor?: string,\n\t): { blobs: Array<{ cid: string; recordUri: string }>; cursor?: string } {\n\t\tconst blobs: Array<{ cid: string; recordUri: string }> = [];\n\n\t\t// Get blobs referenced but not in imported_blobs\n\t\tconst query = cursor\n\t\t\t? `SELECT rb.blobCid, rb.recordUri FROM record_blob rb\n\t\t\t\t LEFT JOIN imported_blobs ib ON rb.blobCid = ib.cid\n\t\t\t\t WHERE ib.cid IS NULL AND rb.blobCid > ?\n\t\t\t\t ORDER BY rb.blobCid\n\t\t\t\t LIMIT ?`\n\t\t\t: `SELECT rb.blobCid, rb.recordUri FROM record_blob rb\n\t\t\t\t LEFT JOIN imported_blobs ib ON rb.blobCid = ib.cid\n\t\t\t\t WHERE ib.cid IS NULL\n\t\t\t\t ORDER BY rb.blobCid\n\t\t\t\t LIMIT ?`;\n\n\t\tconst rows = cursor\n\t\t\t? this.sql.exec(query, cursor, limit + 1).toArray()\n\t\t\t: this.sql.exec(query, limit + 1).toArray();\n\n\t\tfor (const row of rows.slice(0, limit)) {\n\t\t\tblobs.push({\n\t\t\t\tcid: row.blobCid as string,\n\t\t\t\trecordUri: row.recordUri as string,\n\t\t\t});\n\t\t}\n\n\t\tconst hasMore = rows.length > limit;\n\t\tconst nextCursor = hasMore ? blobs[blobs.length - 1]?.cid : undefined;\n\n\t\treturn { blobs, cursor: nextCursor };\n\t}\n\n\t/**\n\t * Clear all blob tracking data (for testing).\n\t */\n\tclearBlobTracking(): void {\n\t\tthis.sql.exec(\"DELETE FROM record_blob\");\n\t\tthis.sql.exec(\"DELETE FROM imported_blobs\");\n\t}\n}\n","import type {\n\tAuthCodeData,\n\tClientMetadata,\n\tOAuthStorage,\n\tPARData,\n\tTokenData,\n} from \"@getcirrus/oauth-provider\";\n\n/**\n * SQLite-backed OAuth storage for Cloudflare Durable Objects.\n *\n * Implements the OAuthStorage interface from @getcirrus/oauth-provider,\n * storing OAuth data in SQLite tables within a Durable Object.\n */\nexport class SqliteOAuthStorage implements OAuthStorage {\n\tconstructor(private sql: SqlStorage) {}\n\n\t/**\n\t * Initialize the OAuth database schema. Should be called once on DO startup.\n\t */\n\tinitSchema(): void {\n\t\tthis.sql.exec(`\n\t\t\t-- Authorization codes (5 min TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_auth_codes (\n\t\t\t\tcode TEXT PRIMARY KEY,\n\t\t\t\tclient_id TEXT NOT NULL,\n\t\t\t\tredirect_uri TEXT NOT NULL,\n\t\t\t\tcode_challenge TEXT NOT NULL,\n\t\t\t\tcode_challenge_method TEXT NOT NULL DEFAULT 'S256',\n\t\t\t\tscope TEXT NOT NULL,\n\t\t\t\tsub TEXT NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_auth_codes_expires ON oauth_auth_codes(expires_at);\n\n\t\t\t-- OAuth tokens\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_tokens (\n\t\t\t\taccess_token TEXT PRIMARY KEY,\n\t\t\t\trefresh_token TEXT NOT NULL UNIQUE,\n\t\t\t\tclient_id TEXT NOT NULL,\n\t\t\t\tsub TEXT NOT NULL,\n\t\t\t\tscope TEXT NOT NULL,\n\t\t\t\tdpop_jkt TEXT,\n\t\t\t\tissued_at INTEGER NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL,\n\t\t\t\trevoked INTEGER NOT NULL DEFAULT 0\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_tokens_refresh ON oauth_tokens(refresh_token);\n\t\t\tCREATE INDEX IF NOT EXISTS idx_tokens_sub ON oauth_tokens(sub);\n\t\t\tCREATE INDEX IF NOT EXISTS idx_tokens_expires ON oauth_tokens(expires_at);\n\n\t\t\t-- Cached client metadata\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_clients (\n\t\t\t\tclient_id TEXT PRIMARY KEY,\n\t\t\t\tclient_name TEXT NOT NULL,\n\t\t\t\tredirect_uris TEXT NOT NULL,\n\t\t\t\tlogo_uri TEXT,\n\t\t\t\tclient_uri TEXT,\n\t\t\t\tcached_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\t-- PAR requests (90 sec TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_par_requests (\n\t\t\t\trequest_uri TEXT PRIMARY KEY,\n\t\t\t\tclient_id TEXT NOT NULL,\n\t\t\t\tparams TEXT NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_par_expires ON oauth_par_requests(expires_at);\n\n\t\t\t-- DPoP nonces for replay prevention (5 min TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_nonces (\n\t\t\t\tnonce TEXT PRIMARY KEY,\n\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_nonces_created ON oauth_nonces(created_at);\n\t\t`);\n\t}\n\n\t/**\n\t * Clean up expired entries. Should be called periodically.\n\t */\n\tcleanup(): void {\n\t\tconst now = Date.now();\n\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes WHERE expires_at < ?\", now);\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_tokens WHERE expires_at < ? AND revoked = 0\",\n\t\t\tnow,\n\t\t);\n\t\tthis.sql.exec(\"DELETE FROM oauth_par_requests WHERE expires_at < ?\", now);\n\t\t// Nonces expire after 5 minutes\n\t\tconst nonceExpiry = now - 5 * 60 * 1000;\n\t\tthis.sql.exec(\"DELETE FROM oauth_nonces WHERE created_at < ?\", nonceExpiry);\n\t}\n\n\t// ============================================\n\t// Authorization Codes\n\t// ============================================\n\n\tasync saveAuthCode(code: string, data: AuthCodeData): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO oauth_auth_codes\n\t\t\t(code, client_id, redirect_uri, code_challenge, code_challenge_method, scope, sub, expires_at)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\tcode,\n\t\t\tdata.clientId,\n\t\t\tdata.redirectUri,\n\t\t\tdata.codeChallenge,\n\t\t\tdata.codeChallengeMethod,\n\t\t\tdata.scope,\n\t\t\tdata.sub,\n\t\t\tdata.expiresAt,\n\t\t);\n\t}\n\n\tasync getAuthCode(code: string): Promise<AuthCodeData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT client_id, redirect_uri, code_challenge, code_challenge_method, scope, sub, expires_at\n\t\t\t\tFROM oauth_auth_codes WHERE code = ?`,\n\t\t\t\tcode,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst expiresAt = row.expires_at as number;\n\n\t\tif (Date.now() > expiresAt) {\n\t\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes WHERE code = ?\", code);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tclientId: row.client_id as string,\n\t\t\tredirectUri: row.redirect_uri as string,\n\t\t\tcodeChallenge: row.code_challenge as string,\n\t\t\tcodeChallengeMethod: row.code_challenge_method as \"S256\",\n\t\t\tscope: row.scope as string,\n\t\t\tsub: row.sub as string,\n\t\t\texpiresAt,\n\t\t};\n\t}\n\n\tasync deleteAuthCode(code: string): Promise<void> {\n\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes WHERE code = ?\", code);\n\t}\n\n\t// ============================================\n\t// Tokens\n\t// ============================================\n\n\tasync saveTokens(data: TokenData): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO oauth_tokens\n\t\t\t(access_token, refresh_token, client_id, sub, scope, dpop_jkt, issued_at, expires_at, revoked)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\tdata.accessToken,\n\t\t\tdata.refreshToken,\n\t\t\tdata.clientId,\n\t\t\tdata.sub,\n\t\t\tdata.scope,\n\t\t\tdata.dpopJkt ?? null,\n\t\t\tdata.issuedAt,\n\t\t\tdata.expiresAt,\n\t\t\tdata.revoked ? 1 : 0,\n\t\t);\n\t}\n\n\tasync getTokenByAccess(accessToken: string): Promise<TokenData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT access_token, refresh_token, client_id, sub, scope, dpop_jkt, issued_at, expires_at, revoked\n\t\t\t\tFROM oauth_tokens WHERE access_token = ?`,\n\t\t\t\taccessToken,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst revoked = Boolean(row.revoked);\n\t\tconst expiresAt = row.expires_at as number;\n\n\t\tif (revoked || Date.now() > expiresAt) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: row.access_token as string,\n\t\t\trefreshToken: row.refresh_token as string,\n\t\t\tclientId: row.client_id as string,\n\t\t\tsub: row.sub as string,\n\t\t\tscope: row.scope as string,\n\t\t\tdpopJkt: (row.dpop_jkt as string) ?? undefined,\n\t\t\tissuedAt: row.issued_at as number,\n\t\t\texpiresAt,\n\t\t\trevoked,\n\t\t};\n\t}\n\n\tasync getTokenByRefresh(refreshToken: string): Promise<TokenData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT access_token, refresh_token, client_id, sub, scope, dpop_jkt, issued_at, expires_at, revoked\n\t\t\t\tFROM oauth_tokens WHERE refresh_token = ?`,\n\t\t\t\trefreshToken,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst revoked = Boolean(row.revoked);\n\n\t\tif (revoked) return null;\n\n\t\treturn {\n\t\t\taccessToken: row.access_token as string,\n\t\t\trefreshToken: row.refresh_token as string,\n\t\t\tclientId: row.client_id as string,\n\t\t\tsub: row.sub as string,\n\t\t\tscope: row.scope as string,\n\t\t\tdpopJkt: (row.dpop_jkt as string) ?? undefined,\n\t\t\tissuedAt: row.issued_at as number,\n\t\t\texpiresAt: row.expires_at as number,\n\t\t\trevoked,\n\t\t};\n\t}\n\n\tasync revokeToken(accessToken: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"UPDATE oauth_tokens SET revoked = 1 WHERE access_token = ?\",\n\t\t\taccessToken,\n\t\t);\n\t}\n\n\tasync revokeAllTokens(sub: string): Promise<void> {\n\t\tthis.sql.exec(\"UPDATE oauth_tokens SET revoked = 1 WHERE sub = ?\", sub);\n\t}\n\n\t// ============================================\n\t// Clients\n\t// ============================================\n\n\tasync saveClient(clientId: string, metadata: ClientMetadata): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT OR REPLACE INTO oauth_clients\n\t\t\t(client_id, client_name, redirect_uris, logo_uri, client_uri, cached_at)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?)`,\n\t\t\tclientId,\n\t\t\tmetadata.clientName,\n\t\t\tJSON.stringify(metadata.redirectUris),\n\t\t\tmetadata.logoUri ?? null,\n\t\t\tmetadata.clientUri ?? null,\n\t\t\tmetadata.cachedAt ?? Date.now(),\n\t\t);\n\t}\n\n\tasync getClient(clientId: string): Promise<ClientMetadata | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT client_id, client_name, redirect_uris, logo_uri, client_uri, cached_at\n\t\t\t\tFROM oauth_clients WHERE client_id = ?`,\n\t\t\t\tclientId,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\treturn {\n\t\t\tclientId: row.client_id as string,\n\t\t\tclientName: row.client_name as string,\n\t\t\tredirectUris: JSON.parse(row.redirect_uris as string) as string[],\n\t\t\tlogoUri: (row.logo_uri as string) ?? undefined,\n\t\t\tclientUri: (row.client_uri as string) ?? undefined,\n\t\t\tcachedAt: row.cached_at as number,\n\t\t};\n\t}\n\n\t// ============================================\n\t// PAR Requests\n\t// ============================================\n\n\tasync savePAR(requestUri: string, data: PARData): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO oauth_par_requests (request_uri, client_id, params, expires_at)\n\t\t\tVALUES (?, ?, ?, ?)`,\n\t\t\trequestUri,\n\t\t\tdata.clientId,\n\t\t\tJSON.stringify(data.params),\n\t\t\tdata.expiresAt,\n\t\t);\n\t}\n\n\tasync getPAR(requestUri: string): Promise<PARData | null> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT client_id, params, expires_at FROM oauth_par_requests WHERE request_uri = ?`,\n\t\t\t\trequestUri,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) return null;\n\n\t\tconst row = rows[0]!;\n\t\tconst expiresAt = row.expires_at as number;\n\n\t\tif (Date.now() > expiresAt) {\n\t\t\tthis.sql.exec(\n\t\t\t\t\"DELETE FROM oauth_par_requests WHERE request_uri = ?\",\n\t\t\t\trequestUri,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tclientId: row.client_id as string,\n\t\t\tparams: JSON.parse(row.params as string) as Record<string, string>,\n\t\t\texpiresAt,\n\t\t};\n\t}\n\n\tasync deletePAR(requestUri: string): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_par_requests WHERE request_uri = ?\",\n\t\t\trequestUri,\n\t\t);\n\t}\n\n\t// ============================================\n\t// DPoP Nonces\n\t// ============================================\n\n\tasync checkAndSaveNonce(nonce: string): Promise<boolean> {\n\t\t// Check if nonce already exists\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT 1 FROM oauth_nonces WHERE nonce = ? LIMIT 1\", nonce)\n\t\t\t.toArray();\n\n\t\tif (rows.length > 0) {\n\t\t\treturn false; // Nonce already used\n\t\t}\n\n\t\t// Save the nonce\n\t\tthis.sql.exec(\n\t\t\t\"INSERT INTO oauth_nonces (nonce, created_at) VALUES (?, ?)\",\n\t\t\tnonce,\n\t\t\tDate.now(),\n\t\t);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Clear all OAuth data (for testing).\n\t */\n\tdestroy(): void {\n\t\tthis.sql.exec(\"DELETE FROM oauth_auth_codes\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_tokens\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_clients\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_par_requests\");\n\t\tthis.sql.exec(\"DELETE FROM oauth_nonces\");\n\t}\n}\n","import {\n\tencode as cborEncode,\n\tdecode as cborDecode,\n\ttype LexValue,\n} from \"@atproto/lex-cbor\";\nimport { CID } from \"@atproto/lex-data\";\nimport { blocksToCarFile, type BlockMap } from \"@atproto/repo\";\nimport type { RecordWriteOp } from \"@atproto/repo\";\n\n/**\n * Commit event payload for the firehose\n */\nexport interface CommitEvent {\n\tseq: number;\n\trebase: boolean;\n\ttooBig: boolean;\n\trepo: string;\n\tcommit: CID;\n\trev: string;\n\tsince: string | null;\n\tblocks: Uint8Array;\n\tops: RepoOp[];\n\tblobs: CID[];\n\ttime: string;\n}\n\n/**\n * Repository operation in a commit\n */\nexport interface RepoOp {\n\taction: \"create\" | \"update\" | \"delete\";\n\tpath: string;\n\tcid: CID | null;\n}\n\n/**\n * Sequenced event wrapper\n */\nexport interface SeqEvent {\n\tseq: number;\n\ttype: \"commit\";\n\tevent: CommitEvent;\n\ttime: string;\n}\n\n/**\n * Data needed to sequence a commit\n */\nexport interface CommitData {\n\tdid: string;\n\tcommit: CID;\n\trev: string;\n\tsince: string | null;\n\tnewBlocks: BlockMap;\n\tops: Array<RecordWriteOp & { cid?: CID | null }>;\n}\n\n/**\n * Sequencer manages the firehose event log.\n *\n * Stores commit events in SQLite and provides methods for:\n * - Sequencing new commits\n * - Backfilling events from a cursor\n * - Getting the latest sequence number\n */\nexport class Sequencer {\n\tconstructor(private sql: SqlStorage) {}\n\n\t/**\n\t * Add a commit to the firehose sequence.\n\t * Returns the complete sequenced event for broadcasting.\n\t */\n\tasync sequenceCommit(data: CommitData): Promise<SeqEvent> {\n\t\t// Create CAR slice with commit diff\n\t\tconst carBytes = await blocksToCarFile(data.commit, data.newBlocks);\n\t\tconst time = new Date().toISOString();\n\n\t\t// Build event payload\n\t\tconst eventPayload: Omit<CommitEvent, \"seq\"> = {\n\t\t\trepo: data.did,\n\t\t\tcommit: data.commit,\n\t\t\trev: data.rev,\n\t\t\tsince: data.since,\n\t\t\tblocks: carBytes,\n\t\t\tops: data.ops.map(\n\t\t\t\t(op): RepoOp => ({\n\t\t\t\t\taction: op.action as \"create\" | \"update\" | \"delete\",\n\t\t\t\t\tpath: `${op.collection}/${op.rkey}`,\n\t\t\t\t\tcid: (\"cid\" in op && op.cid ? op.cid : null) as CID | null,\n\t\t\t\t}),\n\t\t\t),\n\t\t\trebase: false,\n\t\t\ttooBig: carBytes.length > 1_000_000,\n\t\t\tblobs: [],\n\t\t\ttime,\n\t\t};\n\n\t\t// Store in SQLite\n\t\t// Type assertion: CBOR handles CID/Uint8Array serialization\n\t\tconst payload = cborEncode(eventPayload as {} as LexValue);\n\t\tconst result = this.sql\n\t\t\t.exec(\n\t\t\t\t`INSERT INTO firehose_events (event_type, payload)\n VALUES ('commit', ?)\n RETURNING seq`,\n\t\t\t\tpayload,\n\t\t\t)\n\t\t\t.one();\n\n\t\tconst seq = result.seq as number;\n\n\t\treturn {\n\t\t\tseq,\n\t\t\ttype: \"commit\",\n\t\t\tevent: {\n\t\t\t\t...eventPayload,\n\t\t\t\tseq,\n\t\t\t},\n\t\t\ttime,\n\t\t};\n\t}\n\n\t/**\n\t * Get events from a cursor position.\n\t * Returns up to `limit` events after the cursor.\n\t */\n\tasync getEventsSince(cursor: number, limit = 100): Promise<SeqEvent[]> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT seq, event_type, payload, created_at\n FROM firehose_events\n WHERE seq > ?\n ORDER BY seq ASC\n LIMIT ?`,\n\t\t\t\tcursor,\n\t\t\t\tlimit,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\treturn rows.map((row) => {\n\t\t\tconst payload = new Uint8Array(row.payload as ArrayBuffer);\n\t\t\tconst decoded = cborDecode(payload);\n\n\t\t\treturn {\n\t\t\t\tseq: row.seq as number,\n\t\t\t\ttype: \"commit\",\n\t\t\t\tevent: {\n\t\t\t\t\t...(decoded as unknown as Omit<CommitEvent, \"seq\">),\n\t\t\t\t\tseq: row.seq as number,\n\t\t\t\t},\n\t\t\t\ttime: row.created_at as string,\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * Get the latest sequence number.\n\t * Returns 0 if no events have been sequenced yet.\n\t */\n\tgetLatestSeq(): number {\n\t\tconst result = this.sql\n\t\t\t.exec(\"SELECT MAX(seq) as seq FROM firehose_events\")\n\t\t\t.one();\n\t\treturn (result?.seq as number) ?? 0;\n\t}\n\n\t/**\n\t * Prune old events to keep the log from growing indefinitely.\n\t * Keeps the most recent `keepCount` events.\n\t */\n\tasync pruneOldEvents(keepCount = 10000): Promise<void> {\n\t\tthis.sql.exec(\n\t\t\t`DELETE FROM firehose_events\n WHERE seq < (SELECT MAX(seq) - ? FROM firehose_events)`,\n\t\t\tkeepCount,\n\t\t);\n\t}\n}\n","import { CID } from \"@atproto/lex-data\";\nimport { cidForRawBytes } from \"@atproto/lex-cbor\";\n\nexport interface BlobRef {\n\t$type: \"blob\";\n\tref: { $link: string };\n\tmimeType: string;\n\tsize: number;\n}\n\n/**\n * BlobStore manages blob storage in R2.\n * Blobs are stored with CID-based keys prefixed by the account's DID.\n */\nexport class BlobStore {\n\tconstructor(\n\t\tprivate r2: R2Bucket,\n\t\tprivate did: string,\n\t) {}\n\n\t/**\n\t * Upload a blob to R2 and return a BlobRef.\n\t */\n\tasync putBlob(bytes: Uint8Array, mimeType: string): Promise<BlobRef> {\n\t\t// Compute CID using SHA-256 (RAW codec)\n\t\tconst cid = await cidForRawBytes(bytes);\n\n\t\t// Store in R2 with DID prefix for isolation\n\t\tconst key = `${this.did}/${cid.toString()}`;\n\t\tawait this.r2.put(key, bytes, {\n\t\t\thttpMetadata: { contentType: mimeType },\n\t\t});\n\n\t\treturn {\n\t\t\t$type: \"blob\",\n\t\t\tref: { $link: cid.toString() },\n\t\t\tmimeType,\n\t\t\tsize: bytes.length,\n\t\t};\n\t}\n\n\t/**\n\t * Retrieve a blob from R2 by CID.\n\t */\n\tasync getBlob(cid: CID): Promise<R2ObjectBody | null> {\n\t\tconst key = `${this.did}/${cid.toString()}`;\n\t\treturn this.r2.get(key);\n\t}\n\n\t/**\n\t * Check if a blob exists in R2.\n\t */\n\tasync hasBlob(cid: CID): Promise<boolean> {\n\t\tconst key = `${this.did}/${cid.toString()}`;\n\t\tconst head = await this.r2.head(key);\n\t\treturn head !== null;\n\t}\n}\n","import { DurableObject } from \"cloudflare:workers\";\nimport {\n\tRepo,\n\tWriteOpAction,\n\tBlockMap,\n\tblocksToCarFile,\n\treadCarWithRoot,\n\ttype RecordCreateOp,\n\ttype RecordUpdateOp,\n\ttype RecordDeleteOp,\n\ttype RecordWriteOp,\n} from \"@atproto/repo\";\nimport type { RepoRecord } from \"@atproto/lexicon\";\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { CID, isCid, asCid, isBlobRef } from \"@atproto/lex-data\";\nimport { TID } from \"@atproto/common-web\";\nimport { AtUri } from \"@atproto/syntax\";\nimport { encode as cborEncode } from \"@atproto/lex-cbor\";\nimport { SqliteRepoStorage } from \"./storage\";\nimport { SqliteOAuthStorage } from \"./oauth-storage\";\nimport { Sequencer, type SeqEvent, type CommitData } from \"./sequencer\";\nimport { BlobStore, type BlobRef } from \"./blobs\";\nimport type { PDSEnv } from \"./types\";\n\n/**\n * Account Durable Object - manages a single user's AT Protocol repository.\n *\n * This DO provides:\n * - SQLite-backed block storage for the repository\n * - AT Protocol Repo instance for repository operations\n * - Firehose WebSocket connections\n * - Sequence number management\n */\nexport class AccountDurableObject extends DurableObject<PDSEnv> {\n\tprivate storage: SqliteRepoStorage | null = null;\n\tprivate oauthStorage: SqliteOAuthStorage | null = null;\n\tprivate repo: Repo | null = null;\n\tprivate keypair: Secp256k1Keypair | null = null;\n\tprivate sequencer: Sequencer | null = null;\n\tprivate blobStore: BlobStore | null = null;\n\tprivate storageInitialized = false;\n\tprivate repoInitialized = false;\n\n\tconstructor(ctx: DurableObjectState, env: PDSEnv) {\n\t\tsuper(ctx, env);\n\n\t\t// Validate required environment variables at startup\n\t\tif (!env.SIGNING_KEY) {\n\t\t\tthrow new Error(\"Missing required environment variable: SIGNING_KEY\");\n\t\t}\n\t\tif (!env.DID) {\n\t\t\tthrow new Error(\"Missing required environment variable: DID\");\n\t\t}\n\n\t\t// Initialize BlobStore if R2 bucket is available\n\t\tif (env.BLOBS) {\n\t\t\tthis.blobStore = new BlobStore(env.BLOBS, env.DID);\n\t\t}\n\t}\n\n\t/**\n\t * Initialize the storage adapter. Called lazily on first storage access.\n\t */\n\tprivate async ensureStorageInitialized(): Promise<void> {\n\t\tif (!this.storageInitialized) {\n\t\t\tawait this.ctx.blockConcurrencyWhile(async () => {\n\t\t\t\tif (this.storageInitialized) return; // Double-check after acquiring lock\n\n\t\t\t\t// Determine initial active state from env var (default true for new accounts)\n\t\t\t\tconst initialActive =\n\t\t\t\t\tthis.env.INITIAL_ACTIVE === undefined ||\n\t\t\t\t\tthis.env.INITIAL_ACTIVE === \"true\" ||\n\t\t\t\t\tthis.env.INITIAL_ACTIVE === \"1\";\n\n\t\t\t\tthis.storage = new SqliteRepoStorage(this.ctx.storage.sql);\n\t\t\t\tthis.storage.initSchema(initialActive);\n\t\t\t\tthis.oauthStorage = new SqliteOAuthStorage(this.ctx.storage.sql);\n\t\t\t\tthis.oauthStorage.initSchema();\n\t\t\t\tthis.sequencer = new Sequencer(this.ctx.storage.sql);\n\t\t\t\tthis.storageInitialized = true;\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Initialize the Repo instance. Called lazily on first repo access.\n\t */\n\tprivate async ensureRepoInitialized(): Promise<void> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\tif (!this.repoInitialized) {\n\t\t\tawait this.ctx.blockConcurrencyWhile(async () => {\n\t\t\t\tif (this.repoInitialized) return; // Double-check after acquiring lock\n\n\t\t\t\t// Load signing key\n\t\t\t\tthis.keypair = await Secp256k1Keypair.import(this.env.SIGNING_KEY);\n\n\t\t\t\t// Load or create repo\n\t\t\t\tconst root = await this.storage!.getRoot();\n\t\t\t\tif (root) {\n\t\t\t\t\tthis.repo = await Repo.load(this.storage!, root);\n\t\t\t\t} else {\n\t\t\t\t\tthis.repo = await Repo.create(\n\t\t\t\t\t\tthis.storage!,\n\t\t\t\t\t\tthis.env.DID,\n\t\t\t\t\t\tthis.keypair,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.repoInitialized = true;\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Get the storage adapter for direct access (used by tests and internal operations).\n\t */\n\tasync getStorage(): Promise<SqliteRepoStorage> {\n\t\tawait this.ensureStorageInitialized();\n\t\treturn this.storage!;\n\t}\n\n\t/**\n\t * Get the OAuth storage adapter for OAuth operations.\n\t */\n\tasync getOAuthStorage(): Promise<SqliteOAuthStorage> {\n\t\tawait this.ensureStorageInitialized();\n\t\treturn this.oauthStorage!;\n\t}\n\n\t/**\n\t * Get the Repo instance for repository operations.\n\t */\n\tasync getRepo(): Promise<Repo> {\n\t\tawait this.ensureRepoInitialized();\n\t\treturn this.repo!;\n\t}\n\n\t/**\n\t * Ensure the account is active. Throws error if deactivated.\n\t */\n\tasync ensureActive(): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tconst isActive = await storage.getActive();\n\t\tif (!isActive) {\n\t\t\tthrow new Error(\n\t\t\t\t\"AccountDeactivated: Account is deactivated. Call activateAccount to enable writes.\",\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the signing keypair for repository operations.\n\t */\n\tasync getKeypair(): Promise<Secp256k1Keypair> {\n\t\tawait this.ensureRepoInitialized();\n\t\treturn this.keypair!;\n\t}\n\n\t/**\n\t * Update the Repo instance after mutations.\n\t */\n\tasync setRepo(repo: Repo): Promise<void> {\n\t\tthis.repo = repo;\n\t}\n\n\t/**\n\t * RPC method: Get repo metadata for describeRepo\n\t */\n\tasync rpcDescribeRepo(): Promise<{\n\t\tdid: string;\n\t\tcollections: string[];\n\t\tcid: string;\n\t}> {\n\t\tconst repo = await this.getRepo();\n\t\tconst collections: string[] = [];\n\t\tconst seenCollections = new Set<string>();\n\n\t\tfor await (const record of repo.walkRecords()) {\n\t\t\tif (!seenCollections.has(record.collection)) {\n\t\t\t\tseenCollections.add(record.collection);\n\t\t\t\tcollections.push(record.collection);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tdid: repo.did,\n\t\t\tcollections,\n\t\t\tcid: repo.cid.toString(),\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Get a single record\n\t */\n\tasync rpcGetRecord(\n\t\tcollection: string,\n\t\trkey: string,\n\t): Promise<{\n\t\tcid: string;\n\t\trecord: Rpc.Serializable<any>;\n\t} | null> {\n\t\tconst repo = await this.getRepo();\n\n\t\t// Get the CID from the MST\n\t\tconst dataKey = `${collection}/${rkey}`;\n\t\tconst recordCid = await repo.data.get(dataKey);\n\t\tif (!recordCid) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst record = await repo.getRecord(collection, rkey);\n\n\t\tif (!record) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tcid: recordCid.toString(),\n\t\t\trecord: serializeRecord(record) as Rpc.Serializable<any>,\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: List records in a collection\n\t */\n\tasync rpcListRecords(\n\t\tcollection: string,\n\t\topts: {\n\t\t\tlimit: number;\n\t\t\tcursor?: string;\n\t\t\treverse?: boolean;\n\t\t},\n\t): Promise<{\n\t\trecords: Array<{ uri: string; cid: string; value: unknown }>;\n\t\tcursor?: string;\n\t}> {\n\t\tconst repo = await this.getRepo();\n\t\tconst records = [];\n\t\tconst startFrom = opts.cursor || `${collection}/`;\n\n\t\tfor await (const record of repo.walkRecords(startFrom)) {\n\t\t\tif (record.collection !== collection) {\n\t\t\t\tif (records.length > 0) break;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trecords.push({\n\t\t\t\turi: AtUri.make(repo.did, record.collection, record.rkey).toString(),\n\t\t\t\tcid: record.cid.toString(),\n\t\t\t\tvalue: serializeRecord(record.record),\n\t\t\t});\n\n\t\t\tif (records.length >= opts.limit + 1) break;\n\t\t}\n\n\t\tif (opts.reverse) {\n\t\t\trecords.reverse();\n\t\t}\n\n\t\tconst hasMore = records.length > opts.limit;\n\t\tconst results = hasMore ? records.slice(0, opts.limit) : records;\n\t\tconst cursor = hasMore\n\t\t\t? `${collection}/${results[results.length - 1]?.uri.split(\"/\").pop() ?? \"\"}`\n\t\t\t: undefined;\n\n\t\treturn { records: results, cursor };\n\t}\n\n\t/**\n\t * RPC method: Create a record\n\t */\n\tasync rpcCreateRecord(\n\t\tcollection: string,\n\t\trkey: string | undefined,\n\t\trecord: unknown,\n\t): Promise<{\n\t\turi: string;\n\t\tcid: string;\n\t\tcommit: { cid: string; rev: string };\n\t}> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\tconst actualRkey = rkey || TID.nextStr();\n\t\tconst createOp: RecordCreateOp = {\n\t\t\taction: WriteOpAction.Create,\n\t\t\tcollection,\n\t\t\trkey: actualRkey,\n\t\t\trecord: record as RepoRecord,\n\t\t};\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites([createOp], keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Get the CID for the created record from the MST\n\t\tconst dataKey = `${collection}/${actualRkey}`;\n\t\tconst recordCid = await this.repo.data.get(dataKey);\n\n\t\tif (!recordCid) {\n\t\t\tthrow new Error(`Failed to create record: ${collection}/${actualRkey}`);\n\t\t}\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\t// Get blocks that changed\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\t// Include the record CID in the op for the firehose\n\t\t\tconst opWithCid = { ...createOp, cid: recordCid };\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: [opWithCid],\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\turi: AtUri.make(this.repo.did, collection, actualRkey).toString(),\n\t\t\tcid: recordCid.toString(),\n\t\t\tcommit: {\n\t\t\t\tcid: this.repo.cid.toString(),\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Delete a record\n\t */\n\tasync rpcDeleteRecord(\n\t\tcollection: string,\n\t\trkey: string,\n\t): Promise<{ commit: { cid: string; rev: string } } | null> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\tconst existing = await repo.getRecord(collection, rkey);\n\t\tif (!existing) return null;\n\n\t\tconst deleteOp: RecordDeleteOp = {\n\t\t\taction: WriteOpAction.Delete,\n\t\t\tcollection,\n\t\t\trkey,\n\t\t};\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites([deleteOp], keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\t// Get blocks that changed\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: [deleteOp],\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\tcommit: {\n\t\t\t\tcid: updatedRepo.cid.toString(),\n\t\t\t\trev: updatedRepo.commit.rev,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Put a record (create or update)\n\t */\n\tasync rpcPutRecord(\n\t\tcollection: string,\n\t\trkey: string,\n\t\trecord: unknown,\n\t): Promise<{\n\t\turi: string;\n\t\tcid: string;\n\t\tcommit: { cid: string; rev: string };\n\t\tvalidationStatus: string;\n\t}> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\t// Check if record exists to determine create vs update\n\t\tconst existing = await repo.getRecord(collection, rkey);\n\t\tconst isUpdate = existing !== null;\n\n\t\tconst op: RecordWriteOp = isUpdate\n\t\t\t? ({\n\t\t\t\t\taction: WriteOpAction.Update,\n\t\t\t\t\tcollection,\n\t\t\t\t\trkey,\n\t\t\t\t\trecord: record as RepoRecord,\n\t\t\t\t} as RecordUpdateOp)\n\t\t\t: ({\n\t\t\t\t\taction: WriteOpAction.Create,\n\t\t\t\t\tcollection,\n\t\t\t\t\trkey,\n\t\t\t\t\trecord: record as RepoRecord,\n\t\t\t\t} as RecordCreateOp);\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites([op], keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Get the CID for the record from the MST\n\t\tconst dataKey = `${collection}/${rkey}`;\n\t\tconst recordCid = await this.repo.data.get(dataKey);\n\n\t\tif (!recordCid) {\n\t\t\tthrow new Error(`Failed to put record: ${collection}/${rkey}`);\n\t\t}\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\tconst opWithCid = { ...op, cid: recordCid };\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: [opWithCid],\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\turi: AtUri.make(this.repo.did, collection, rkey).toString(),\n\t\t\tcid: recordCid.toString(),\n\t\t\tcommit: {\n\t\t\t\tcid: this.repo.cid.toString(),\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t},\n\t\t\tvalidationStatus: \"valid\",\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Apply multiple writes (batch create/update/delete)\n\t */\n\tasync rpcApplyWrites(\n\t\twrites: Array<{\n\t\t\t$type: string;\n\t\t\tcollection: string;\n\t\t\trkey?: string;\n\t\t\tvalue?: unknown;\n\t\t}>,\n\t): Promise<{\n\t\tcommit: { cid: string; rev: string };\n\t\tresults: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t}>;\n\t}> {\n\t\tawait this.ensureActive();\n\t\tconst repo = await this.getRepo();\n\t\tconst keypair = await this.getKeypair();\n\n\t\t// Convert input writes to RecordWriteOp format\n\t\tconst ops: RecordWriteOp[] = [];\n\t\tconst results: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t\tcollection: string;\n\t\t\trkey: string;\n\t\t\taction: WriteOpAction;\n\t\t}> = [];\n\n\t\tfor (const write of writes) {\n\t\t\tif (write.$type === \"com.atproto.repo.applyWrites#create\") {\n\t\t\t\tconst rkey = write.rkey || TID.nextStr();\n\t\t\t\tconst op: RecordCreateOp = {\n\t\t\t\t\taction: WriteOpAction.Create,\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey,\n\t\t\t\t\trecord: write.value as RepoRecord,\n\t\t\t\t};\n\t\t\t\tops.push(op);\n\t\t\t\tresults.push({\n\t\t\t\t\t$type: \"com.atproto.repo.applyWrites#createResult\",\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey,\n\t\t\t\t\taction: WriteOpAction.Create,\n\t\t\t\t});\n\t\t\t} else if (write.$type === \"com.atproto.repo.applyWrites#update\") {\n\t\t\t\tif (!write.rkey) {\n\t\t\t\t\tthrow new Error(\"Update requires rkey\");\n\t\t\t\t}\n\t\t\t\tconst op: RecordUpdateOp = {\n\t\t\t\t\taction: WriteOpAction.Update,\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t\trecord: write.value as RepoRecord,\n\t\t\t\t};\n\t\t\t\tops.push(op);\n\t\t\t\tresults.push({\n\t\t\t\t\t$type: \"com.atproto.repo.applyWrites#updateResult\",\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t\taction: WriteOpAction.Update,\n\t\t\t\t});\n\t\t\t} else if (write.$type === \"com.atproto.repo.applyWrites#delete\") {\n\t\t\t\tif (!write.rkey) {\n\t\t\t\t\tthrow new Error(\"Delete requires rkey\");\n\t\t\t\t}\n\t\t\t\tconst op: RecordDeleteOp = {\n\t\t\t\t\taction: WriteOpAction.Delete,\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t};\n\t\t\t\tops.push(op);\n\t\t\t\tresults.push({\n\t\t\t\t\t$type: \"com.atproto.repo.applyWrites#deleteResult\",\n\t\t\t\t\tcollection: write.collection,\n\t\t\t\t\trkey: write.rkey,\n\t\t\t\t\taction: WriteOpAction.Delete,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error(`Unknown write type: ${write.$type}`);\n\t\t\t}\n\t\t}\n\n\t\tconst prevRev = repo.commit.rev;\n\t\tconst updatedRepo = await repo.applyWrites(ops, keypair);\n\t\tthis.repo = updatedRepo;\n\n\t\t// Build final results with CIDs and prepare ops with CIDs for firehose\n\t\tconst finalResults: Array<{\n\t\t\t$type: string;\n\t\t\turi?: string;\n\t\t\tcid?: string;\n\t\t\tvalidationStatus?: string;\n\t\t}> = [];\n\t\tconst opsWithCids: Array<RecordWriteOp & { cid?: CID | null }> = [];\n\n\t\tfor (let i = 0; i < results.length; i++) {\n\t\t\tconst result = results[i]!;\n\t\t\tconst op = ops[i]!;\n\n\t\t\tif (result.action === WriteOpAction.Delete) {\n\t\t\t\tfinalResults.push({\n\t\t\t\t\t$type: result.$type,\n\t\t\t\t});\n\t\t\t\topsWithCids.push(op);\n\t\t\t} else {\n\t\t\t\t// Get the CID for create/update\n\t\t\t\tconst dataKey = `${result.collection}/${result.rkey}`;\n\t\t\t\tconst recordCid = await this.repo.data.get(dataKey);\n\t\t\t\tfinalResults.push({\n\t\t\t\t\t$type: result.$type,\n\t\t\t\t\turi: AtUri.make(\n\t\t\t\t\t\tthis.repo.did,\n\t\t\t\t\t\tresult.collection,\n\t\t\t\t\t\tresult.rkey,\n\t\t\t\t\t).toString(),\n\t\t\t\t\tcid: recordCid?.toString(),\n\t\t\t\t\tvalidationStatus: \"valid\",\n\t\t\t\t});\n\t\t\t\t// Include the record CID in the op for the firehose\n\t\t\t\topsWithCids.push({ ...op, cid: recordCid });\n\t\t\t}\n\t\t}\n\n\t\t// Sequence the commit for firehose\n\t\tif (this.sequencer) {\n\t\t\tconst newBlocks = new BlockMap();\n\t\t\tconst rows = this.ctx.storage.sql\n\t\t\t\t.exec(\n\t\t\t\t\t\"SELECT cid, bytes FROM blocks WHERE rev = ?\",\n\t\t\t\t\tthis.repo.commit.rev,\n\t\t\t\t)\n\t\t\t\t.toArray();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\t\tnewBlocks.set(cid, bytes);\n\t\t\t}\n\n\t\t\tconst commitData: CommitData = {\n\t\t\t\tdid: this.repo.did,\n\t\t\t\tcommit: this.repo.cid,\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t\tsince: prevRev,\n\t\t\t\tnewBlocks,\n\t\t\t\tops: opsWithCids,\n\t\t\t};\n\n\t\t\tconst event = await this.sequencer.sequenceCommit(commitData);\n\t\t\tawait this.broadcastCommit(event);\n\t\t}\n\n\t\treturn {\n\t\t\tcommit: {\n\t\t\t\tcid: this.repo.cid.toString(),\n\t\t\t\trev: this.repo.commit.rev,\n\t\t\t},\n\t\t\tresults: finalResults,\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Get repo status\n\t */\n\tasync rpcGetRepoStatus(): Promise<{\n\t\tdid: string;\n\t\thead: string;\n\t\trev: string;\n\t}> {\n\t\tconst repo = await this.getRepo();\n\t\treturn {\n\t\t\tdid: repo.did,\n\t\t\thead: repo.cid.toString(),\n\t\t\trev: repo.commit.rev,\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Export repo as CAR\n\t */\n\tasync rpcGetRepoCar(): Promise<Uint8Array> {\n\t\tconst storage = await this.getStorage();\n\t\tconst root = await storage.getRoot();\n\n\t\tif (!root) {\n\t\t\tthrow new Error(\"No repository root found\");\n\t\t}\n\n\t\t// Get all blocks from SQLite storage\n\t\tconst rows = this.ctx.storage.sql\n\t\t\t.exec(\"SELECT cid, bytes FROM blocks\")\n\t\t\t.toArray();\n\n\t\t// Build BlockMap\n\t\tconst blocks = new BlockMap();\n\t\tfor (const row of rows) {\n\t\t\tconst cid = CID.parse(row.cid as string);\n\t\t\tconst bytes = new Uint8Array(row.bytes as ArrayBuffer);\n\t\t\tblocks.set(cid, bytes);\n\t\t}\n\n\t\t// Use the official CAR builder\n\t\treturn blocksToCarFile(root, blocks);\n\t}\n\n\t/**\n\t * RPC method: Get specific blocks by CID as CAR file\n\t * Used for partial sync and migration.\n\t */\n\tasync rpcGetBlocks(cids: string[]): Promise<Uint8Array> {\n\t\tconst storage = await this.getStorage();\n\t\tconst root = await storage.getRoot();\n\n\t\tif (!root) {\n\t\t\tthrow new Error(\"No repository root found\");\n\t\t}\n\n\t\t// Get requested blocks\n\t\tconst blocks = new BlockMap();\n\t\tfor (const cidStr of cids) {\n\t\t\tconst cid = CID.parse(cidStr);\n\t\t\tconst bytes = await storage.getBytes(cid);\n\t\t\tif (bytes) {\n\t\t\t\tblocks.set(cid, bytes);\n\t\t\t}\n\t\t}\n\n\t\t// Return CAR file with requested blocks\n\t\treturn blocksToCarFile(root, blocks);\n\t}\n\n\t/**\n\t * RPC method: Import repo from CAR file\n\t * This is used for account migration - importing an existing repository\n\t * from another PDS.\n\t */\n\tasync rpcImportRepo(carBytes: Uint8Array): Promise<{\n\t\tdid: string;\n\t\trev: string;\n\t\tcid: string;\n\t}> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\t// Check if account is active - only allow imports on deactivated accounts\n\t\tconst isActive = await this.storage!.getActive();\n\t\tconst existingRoot = await this.storage!.getRoot();\n\n\t\tif (isActive && existingRoot) {\n\t\t\t// Account is active - reject import to prevent accidental overwrites\n\t\t\tthrow new Error(\n\t\t\t\t\"Repository already exists. Cannot import over existing repository.\",\n\t\t\t);\n\t\t}\n\n\t\t// If deactivated and repo exists, clear it first\n\t\tif (existingRoot) {\n\t\t\tawait this.storage!.destroy();\n\t\t\tthis.repo = null;\n\t\t\tthis.repoInitialized = false;\n\t\t}\n\n\t\t// Use official @atproto/repo utilities to read and validate CAR\n\t\t// readCarWithRoot validates single root requirement and returns BlockMap\n\t\tconst { root: rootCid, blocks } = await readCarWithRoot(carBytes);\n\n\t\t// Import all blocks into storage using putMany (more efficient than individual putBlock)\n\t\tconst importRev = TID.nextStr();\n\t\tawait this.storage!.putMany(blocks, importRev);\n\n\t\t// Load the repo to verify it's valid and get the actual revision\n\t\tthis.keypair = await Secp256k1Keypair.import(this.env.SIGNING_KEY);\n\t\tthis.repo = await Repo.load(this.storage!, rootCid);\n\n\t\t// Persist the root CID in storage so getRoot() works correctly\n\t\tawait this.storage!.updateRoot(rootCid, this.repo.commit.rev);\n\n\t\t// Verify the DID matches to prevent incorrect migrations\n\t\tif (this.repo.did !== this.env.DID) {\n\t\t\t// Clean up imported blocks\n\t\t\tawait this.storage!.destroy();\n\t\t\tthrow new Error(\n\t\t\t\t`DID mismatch: CAR file contains DID ${this.repo.did}, but expected ${this.env.DID}`,\n\t\t\t);\n\t\t}\n\n\t\tthis.repoInitialized = true;\n\n\t\t// Extract blob references from all imported records for tracking\n\t\tfor await (const record of this.repo.walkRecords()) {\n\t\t\tconst blobCids = extractBlobCids(record.record);\n\t\t\tif (blobCids.length > 0) {\n\t\t\t\tconst uri = AtUri.make(\n\t\t\t\t\tthis.repo.did,\n\t\t\t\t\trecord.collection,\n\t\t\t\t\trecord.rkey,\n\t\t\t\t).toString();\n\t\t\t\tthis.storage!.addRecordBlobs(uri, blobCids);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tdid: this.repo.did,\n\t\t\trev: this.repo.commit.rev,\n\t\t\tcid: rootCid.toString(),\n\t\t};\n\t}\n\n\t/**\n\t * RPC method: Upload a blob to R2\n\t */\n\tasync rpcUploadBlob(bytes: Uint8Array, mimeType: string): Promise<BlobRef> {\n\t\tif (!this.blobStore) {\n\t\t\tthrow new Error(\"Blob storage not configured\");\n\t\t}\n\n\t\t// Enforce size limit (5MB)\n\t\tconst MAX_BLOB_SIZE = 5 * 1024 * 1024;\n\t\tif (bytes.length > MAX_BLOB_SIZE) {\n\t\t\tthrow new Error(\n\t\t\t\t`Blob too large: ${bytes.length} bytes (max ${MAX_BLOB_SIZE})`,\n\t\t\t);\n\t\t}\n\n\t\tconst blobRef = await this.blobStore.putBlob(bytes, mimeType);\n\n\t\t// Track the imported blob for migration progress\n\t\tconst storage = await this.getStorage();\n\t\tstorage.trackImportedBlob(blobRef.ref.$link, bytes.length, mimeType);\n\n\t\treturn blobRef;\n\t}\n\n\t/**\n\t * RPC method: Get a blob from R2\n\t */\n\tasync rpcGetBlob(cidStr: string): Promise<R2ObjectBody | null> {\n\t\tif (!this.blobStore) {\n\t\t\tthrow new Error(\"Blob storage not configured\");\n\t\t}\n\n\t\tconst cid = CID.parse(cidStr);\n\t\treturn this.blobStore.getBlob(cid);\n\t}\n\n\t/**\n\t * Encode a firehose frame (header + body CBOR).\n\t */\n\tprivate encodeFrame(header: object, body: object): Uint8Array {\n\t\tconst headerBytes = cborEncode(header as any);\n\t\tconst bodyBytes = cborEncode(body as any);\n\n\t\tconst frame = new Uint8Array(headerBytes.length + bodyBytes.length);\n\t\tframe.set(headerBytes, 0);\n\t\tframe.set(bodyBytes, headerBytes.length);\n\n\t\treturn frame;\n\t}\n\n\t/**\n\t * Encode a commit event frame.\n\t */\n\tprivate encodeCommitFrame(event: SeqEvent): Uint8Array {\n\t\tconst header = { op: 1, t: \"#commit\" };\n\t\treturn this.encodeFrame(header, event.event);\n\t}\n\n\t/**\n\t * Encode an error frame.\n\t */\n\tprivate encodeErrorFrame(error: string, message: string): Uint8Array {\n\t\tconst header = { op: -1 };\n\t\tconst body = { error, message };\n\t\treturn this.encodeFrame(header, body);\n\t}\n\n\t/**\n\t * Backfill firehose events from a cursor.\n\t */\n\tprivate async backfillFirehose(ws: WebSocket, cursor: number): Promise<void> {\n\t\tif (!this.sequencer) {\n\t\t\tthrow new Error(\"Sequencer not initialized\");\n\t\t}\n\n\t\tconst latestSeq = this.sequencer.getLatestSeq();\n\n\t\t// Check if cursor is in the future\n\t\tif (cursor > latestSeq) {\n\t\t\tconst frame = this.encodeErrorFrame(\n\t\t\t\t\"FutureCursor\",\n\t\t\t\t\"Cursor is in the future\",\n\t\t\t);\n\t\t\tws.send(frame);\n\t\t\tws.close(1008, \"FutureCursor\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Backfill from cursor\n\t\tconst events = await this.sequencer.getEventsSince(cursor, 1000);\n\n\t\tfor (const event of events) {\n\t\t\tconst frame = this.encodeCommitFrame(event);\n\t\t\tws.send(frame);\n\t\t}\n\n\t\t// Update cursor in attachment\n\t\tif (events.length > 0) {\n\t\t\tconst lastEvent = events[events.length - 1];\n\t\t\tif (lastEvent) {\n\t\t\t\tconst attachment = ws.deserializeAttachment() as { cursor: number };\n\t\t\t\tattachment.cursor = lastEvent.seq;\n\t\t\t\tws.serializeAttachment(attachment);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Broadcast a commit event to all connected firehose clients.\n\t */\n\tprivate async broadcastCommit(event: SeqEvent): Promise<void> {\n\t\tconst frame = this.encodeCommitFrame(event);\n\n\t\tfor (const ws of this.ctx.getWebSockets()) {\n\t\t\ttry {\n\t\t\t\tws.send(frame);\n\n\t\t\t\t// Update cursor\n\t\t\t\tconst attachment = ws.deserializeAttachment() as { cursor: number };\n\t\t\t\tattachment.cursor = event.seq;\n\t\t\t\tws.serializeAttachment(attachment);\n\t\t\t} catch (e) {\n\t\t\t\t// Client disconnected, will be cleaned up\n\t\t\t\tconsole.error(\"Error broadcasting to WebSocket:\", e);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Handle WebSocket upgrade for firehose (subscribeRepos).\n\t */\n\tasync handleFirehoseUpgrade(request: Request): Promise<Response> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\tconst url = new URL(request.url);\n\t\tconst cursorParam = url.searchParams.get(\"cursor\");\n\t\tconst cursor = cursorParam ? parseInt(cursorParam, 10) : null;\n\n\t\t// Create WebSocket pair\n\t\tconst pair = new WebSocketPair();\n\t\tconst client = pair[0];\n\t\tconst server = pair[1];\n\n\t\t// Accept with hibernation\n\t\tthis.ctx.acceptWebSocket(server);\n\n\t\t// Store cursor in attachment\n\t\tserver.serializeAttachment({\n\t\t\tcursor: cursor ?? 0,\n\t\t\tconnectedAt: Date.now(),\n\t\t});\n\n\t\t// Backfill if cursor provided\n\t\tif (cursor !== null) {\n\t\t\tawait this.backfillFirehose(server, cursor);\n\t\t}\n\n\t\treturn new Response(null, {\n\t\t\tstatus: 101,\n\t\t\twebSocket: client,\n\t\t});\n\t}\n\n\t/**\n\t * WebSocket message handler (hibernation API).\n\t */\n\toverride webSocketMessage(\n\t\t_ws: WebSocket,\n\t\t_message: string | ArrayBuffer,\n\t): void {\n\t\t// Firehose is server-push only, ignore client messages\n\t}\n\n\t/**\n\t * WebSocket close handler (hibernation API).\n\t */\n\toverride webSocketClose(\n\t\t_ws: WebSocket,\n\t\t_code: number,\n\t\t_reason: string,\n\t\t_wasClean: boolean,\n\t): void {\n\t\t// Cleanup handled automatically by hibernation API\n\t}\n\n\t/**\n\t * WebSocket error handler (hibernation API).\n\t */\n\toverride webSocketError(_ws: WebSocket, error: Error): void {\n\t\tconsole.error(\"WebSocket error:\", error);\n\t}\n\n\t/**\n\t * RPC method: Get user preferences\n\t */\n\tasync rpcGetPreferences(): Promise<{ preferences: unknown[] }> {\n\t\tconst storage = await this.getStorage();\n\t\tconst preferences = await storage.getPreferences();\n\t\treturn { preferences };\n\t}\n\n\t/**\n\t * RPC method: Put user preferences\n\t */\n\tasync rpcPutPreferences(preferences: unknown[]): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tawait storage.putPreferences(preferences);\n\t}\n\n\t/**\n\t * RPC method: Get account activation state\n\t */\n\tasync rpcGetActive(): Promise<boolean> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.getActive();\n\t}\n\n\t/**\n\t * RPC method: Activate account\n\t */\n\tasync rpcActivateAccount(): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tawait storage.setActive(true);\n\t}\n\n\t/**\n\t * RPC method: Deactivate account\n\t */\n\tasync rpcDeactivateAccount(): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tawait storage.setActive(false);\n\t}\n\n\t// ============================================\n\t// Migration Progress RPC Methods\n\t// ============================================\n\n\t/**\n\t * RPC method: Count blocks in storage\n\t */\n\tasync rpcCountBlocks(): Promise<number> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.countBlocks();\n\t}\n\n\t/**\n\t * RPC method: Count records in repository\n\t */\n\tasync rpcCountRecords(): Promise<number> {\n\t\tconst repo = await this.getRepo();\n\t\tlet count = 0;\n\t\tfor await (const _record of repo.walkRecords()) {\n\t\t\tcount++;\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * RPC method: Count expected blobs (referenced in records)\n\t */\n\tasync rpcCountExpectedBlobs(): Promise<number> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.countExpectedBlobs();\n\t}\n\n\t/**\n\t * RPC method: Count imported blobs\n\t */\n\tasync rpcCountImportedBlobs(): Promise<number> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.countImportedBlobs();\n\t}\n\n\t/**\n\t * RPC method: List missing blobs (referenced but not imported)\n\t */\n\tasync rpcListMissingBlobs(\n\t\tlimit: number = 500,\n\t\tcursor?: string,\n\t): Promise<{ blobs: Array<{ cid: string; recordUri: string }>; cursor?: string }> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.listMissingBlobs(limit, cursor);\n\t}\n\n\t/**\n\t * RPC method: Reset migration state.\n\t * Clears imported repo and blob tracking to allow re-import.\n\t * Only works when account is deactivated.\n\t */\n\tasync rpcResetMigration(): Promise<{\n\t\tblocksDeleted: number;\n\t\tblobsCleared: number;\n\t}> {\n\t\tconst storage = await this.getStorage();\n\n\t\t// Only allow reset on deactivated accounts\n\t\tconst isActive = await storage.getActive();\n\t\tif (isActive) {\n\t\t\tthrow new Error(\n\t\t\t\t\"AccountActive: Cannot reset migration on an active account. Deactivate first.\",\n\t\t\t);\n\t\t}\n\n\t\t// Get counts before deletion for reporting\n\t\tconst blocksDeleted = await storage.countBlocks();\n\t\tconst blobsCleared = storage.countImportedBlobs();\n\n\t\t// Clear all blocks and reset repo state\n\t\tawait storage.destroy();\n\n\t\t// Clear blob tracking tables\n\t\tstorage.clearBlobTracking();\n\n\t\t// Reset in-memory repo reference so it gets reinitialized on next access\n\t\tthis.repo = null;\n\t\tthis.repoInitialized = false;\n\n\t\treturn { blocksDeleted, blobsCleared };\n\t}\n\n\t/**\n\t * Emit an identity event to notify downstream services to refresh identity cache.\n\t */\n\tasync rpcEmitIdentityEvent(handle: string): Promise<{ seq: number }> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\tconst time = new Date().toISOString();\n\n\t\t// Get next sequence number\n\t\tconst result = this.ctx.storage.sql\n\t\t\t.exec(\n\t\t\t\t`INSERT INTO firehose_events (event_type, payload)\n\t\t\t\t VALUES ('identity', ?)\n\t\t\t\t RETURNING seq`,\n\t\t\t\tnew Uint8Array(0), // Empty payload, we just need seq\n\t\t\t)\n\t\t\t.one();\n\t\tconst seq = result.seq as number;\n\n\t\t// Build identity event frame\n\t\tconst header = { op: 1, t: \"#identity\" };\n\t\tconst body = {\n\t\t\tseq,\n\t\t\tdid: this.env.DID,\n\t\t\ttime,\n\t\t\thandle,\n\t\t};\n\n\t\tconst headerBytes = cborEncode(\n\t\t\theader as unknown as import(\"@atproto/lex-cbor\").LexValue,\n\t\t);\n\t\tconst bodyBytes = cborEncode(\n\t\t\tbody as unknown as import(\"@atproto/lex-cbor\").LexValue,\n\t\t);\n\t\tconst frame = new Uint8Array(headerBytes.length + bodyBytes.length);\n\t\tframe.set(headerBytes, 0);\n\t\tframe.set(bodyBytes, headerBytes.length);\n\n\t\t// Broadcast to all connected clients\n\t\tfor (const ws of this.ctx.getWebSockets()) {\n\t\t\ttry {\n\t\t\t\tws.send(frame);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(\"Error broadcasting identity event:\", e);\n\t\t\t}\n\t\t}\n\n\t\treturn { seq };\n\t}\n\n\t// ============================================\n\t// OAuth Storage RPC Methods\n\t// These methods proxy to SqliteOAuthStorage since we can't serialize the storage object\n\t// ============================================\n\n\t/** Save an authorization code */\n\tasync rpcSaveAuthCode(\n\t\tcode: string,\n\t\tdata: import(\"@getcirrus/oauth-provider\").AuthCodeData,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.saveAuthCode(code, data);\n\t}\n\n\t/** Get authorization code data */\n\tasync rpcGetAuthCode(\n\t\tcode: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").AuthCodeData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getAuthCode(code);\n\t}\n\n\t/** Delete an authorization code */\n\tasync rpcDeleteAuthCode(code: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.deleteAuthCode(code);\n\t}\n\n\t/** Save token data */\n\tasync rpcSaveTokens(\n\t\tdata: import(\"@getcirrus/oauth-provider\").TokenData,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.saveTokens(data);\n\t}\n\n\t/** Get token data by access token */\n\tasync rpcGetTokenByAccess(\n\t\taccessToken: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").TokenData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getTokenByAccess(accessToken);\n\t}\n\n\t/** Get token data by refresh token */\n\tasync rpcGetTokenByRefresh(\n\t\trefreshToken: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").TokenData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getTokenByRefresh(refreshToken);\n\t}\n\n\t/** Revoke a token */\n\tasync rpcRevokeToken(accessToken: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.revokeToken(accessToken);\n\t}\n\n\t/** Revoke all tokens for a user */\n\tasync rpcRevokeAllTokens(sub: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.revokeAllTokens(sub);\n\t}\n\n\t/** Save client metadata */\n\tasync rpcSaveClient(\n\t\tclientId: string,\n\t\tmetadata: import(\"@getcirrus/oauth-provider\").ClientMetadata,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.saveClient(clientId, metadata);\n\t}\n\n\t/** Get client metadata */\n\tasync rpcGetClient(\n\t\tclientId: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").ClientMetadata | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getClient(clientId);\n\t}\n\n\t/** Save PAR data */\n\tasync rpcSavePAR(\n\t\trequestUri: string,\n\t\tdata: import(\"@getcirrus/oauth-provider\").PARData,\n\t): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.savePAR(requestUri, data);\n\t}\n\n\t/** Get PAR data */\n\tasync rpcGetPAR(\n\t\trequestUri: string,\n\t): Promise<import(\"@getcirrus/oauth-provider\").PARData | null> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.getPAR(requestUri);\n\t}\n\n\t/** Delete PAR data */\n\tasync rpcDeletePAR(requestUri: string): Promise<void> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\tawait storage.deletePAR(requestUri);\n\t}\n\n\t/** Check and save DPoP nonce */\n\tasync rpcCheckAndSaveNonce(nonce: string): Promise<boolean> {\n\t\tconst storage = await this.getOAuthStorage();\n\t\treturn storage.checkAndSaveNonce(nonce);\n\t}\n\n\t/**\n\t * HTTP fetch handler for WebSocket upgrades.\n\t * This is used instead of RPC to avoid WebSocket serialization errors.\n\t */\n\toverride async fetch(request: Request): Promise<Response> {\n\t\t// Only handle WebSocket upgrades via fetch\n\t\tconst url = new URL(request.url);\n\t\tif (url.pathname === \"/xrpc/com.atproto.sync.subscribeRepos\") {\n\t\t\treturn this.handleFirehoseUpgrade(request);\n\t\t}\n\n\t\t// All other requests should use RPC methods, not fetch\n\t\treturn new Response(\"Method not allowed\", { status: 405 });\n\t}\n}\n\n/**\n * Serialize a record for JSON by converting CID objects to { $link: \"...\" } format.\n * CBOR-decoded records contain raw CID objects that need conversion for JSON serialization.\n */\nfunction serializeRecord(obj: unknown): unknown {\n\tif (obj === null || obj === undefined) return obj;\n\n\t// Check if this is a CID object using @atproto/lex-data helper\n\tconst cid = asCid(obj);\n\tif (cid) {\n\t\treturn { $link: cid.toString() };\n\t}\n\n\tif (Array.isArray(obj)) {\n\t\treturn obj.map(serializeRecord);\n\t}\n\n\tif (typeof obj === \"object\") {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, value] of Object.entries(obj)) {\n\t\t\tresult[key] = serializeRecord(value);\n\t\t}\n\t\treturn result;\n\t}\n\n\treturn obj;\n}\n\n/**\n * Extract blob CIDs from a record by recursively searching for blob references.\n * Blob refs have the structure: { $type: \"blob\", ref: CID, mimeType, size }\n */\nfunction extractBlobCids(obj: unknown): string[] {\n\tconst cids: string[] = [];\n\n\tfunction walk(value: unknown): void {\n\t\tif (value === null || value === undefined) return;\n\n\t\t// Check if this is a blob reference using @atproto/lex-data helper\n\t\tif (isBlobRef(value)) {\n\t\t\tcids.push(value.ref.toString());\n\t\t\treturn; // No need to recurse into blob ref properties\n\t\t}\n\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\twalk(item);\n\t\t\t}\n\t\t} else if (typeof value === \"object\") {\n\t\t\t// Recursively walk all properties\n\t\t\tfor (const key of Object.keys(value as Record<string, unknown>)) {\n\t\t\t\twalk((value as Record<string, unknown>)[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\twalk(obj);\n\treturn cids;\n}\n","import { Secp256k1Keypair, randomStr, verifySignature } from \"@atproto/crypto\";\n\nconst MINUTE = 60 * 1000;\n\n/**\n * Shared keypair cache for signing and verification.\n */\nlet cachedKeypair: Secp256k1Keypair | null = null;\nlet cachedSigningKey: string | null = null;\n\n/**\n * Get the signing keypair, with caching.\n * Used for creating service JWTs and verifying them.\n */\nexport async function getSigningKeypair(\n\tsigningKey: string,\n): Promise<Secp256k1Keypair> {\n\tif (cachedKeypair && cachedSigningKey === signingKey) {\n\t\treturn cachedKeypair;\n\t}\n\tcachedKeypair = await Secp256k1Keypair.import(signingKey);\n\tcachedSigningKey = signingKey;\n\treturn cachedKeypair;\n}\n\n/**\n * Service JWT payload structure\n */\nexport interface ServiceJwtPayload {\n\tiss: string; // Issuer (user's DID)\n\taud: string; // Audience (PDS DID)\n\texp: number; // Expiration timestamp\n\tiat?: number; // Issued at timestamp\n\tlxm?: string; // Lexicon method (optional)\n\tjti?: string; // Token ID (optional)\n}\n\ntype ServiceJwtParams = {\n\tiss: string;\n\taud: string;\n\tlxm: string | null;\n\tkeypair: Secp256k1Keypair;\n};\n\nfunction jsonToB64Url(json: Record<string, unknown>): string {\n\treturn Buffer.from(JSON.stringify(json)).toString(\"base64url\");\n}\n\nfunction noUndefinedVals<T extends Record<string, unknown>>(\n\tobj: T,\n): Partial<T> {\n\tconst result: Partial<T> = {};\n\tfor (const [key, val] of Object.entries(obj)) {\n\t\tif (val !== undefined) {\n\t\t\tresult[key as keyof T] = val as T[keyof T];\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Create a service JWT for proxied requests to AppView.\n * The JWT asserts that the PDS vouches for the user identified by `iss`.\n */\nexport async function createServiceJwt(\n\tparams: ServiceJwtParams,\n): Promise<string> {\n\tconst { iss, aud, keypair } = params;\n\tconst iat = Math.floor(Date.now() / 1000);\n\tconst exp = iat + MINUTE / 1000;\n\tconst lxm = params.lxm ?? undefined;\n\tconst jti = randomStr(16, \"hex\");\n\n\tconst header = {\n\t\ttyp: \"JWT\",\n\t\talg: keypair.jwtAlg,\n\t};\n\n\tconst payload = noUndefinedVals({\n\t\tiat,\n\t\tiss,\n\t\taud,\n\t\texp,\n\t\tlxm,\n\t\tjti,\n\t});\n\n\tconst toSignStr = `${jsonToB64Url(header)}.${jsonToB64Url(payload as Record<string, unknown>)}`;\n\tconst toSign = Buffer.from(toSignStr, \"utf8\");\n\tconst sig = Buffer.from(await keypair.sign(toSign));\n\n\treturn `${toSignStr}.${sig.toString(\"base64url\")}`;\n}\n\n/**\n * Verify a service JWT signed with our signing key.\n * These are issued by getServiceAuth and used by external services\n * (like video.bsky.app) to call back to our PDS.\n */\nexport async function verifyServiceJwt(\n\ttoken: string,\n\tsigningKey: string,\n\texpectedAudience: string,\n\texpectedIssuer: string,\n): Promise<ServiceJwtPayload> {\n\tconst parts = token.split(\".\");\n\tif (parts.length !== 3) {\n\t\tthrow new Error(\"Invalid JWT format\");\n\t}\n\n\tconst headerB64 = parts[0]!;\n\tconst payloadB64 = parts[1]!;\n\tconst signatureB64 = parts[2]!;\n\n\t// Decode header\n\tconst header = JSON.parse(Buffer.from(headerB64, \"base64url\").toString());\n\tif (header.alg !== \"ES256K\") {\n\t\tthrow new Error(`Unsupported algorithm: ${header.alg}`);\n\t}\n\n\t// Decode payload\n\tconst payload: ServiceJwtPayload = JSON.parse(\n\t\tBuffer.from(payloadB64, \"base64url\").toString(),\n\t);\n\n\t// Check expiration\n\tconst now = Math.floor(Date.now() / 1000);\n\tif (payload.exp && payload.exp < now) {\n\t\tthrow new Error(\"Token expired\");\n\t}\n\n\t// Check audience (should be our PDS)\n\tif (payload.aud !== expectedAudience) {\n\t\tthrow new Error(`Invalid audience: expected ${expectedAudience}`);\n\t}\n\n\t// Check issuer (should be the user's DID)\n\tif (payload.iss !== expectedIssuer) {\n\t\tthrow new Error(`Invalid issuer: expected ${expectedIssuer}`);\n\t}\n\n\t// Verify signature using shared keypair\n\tconst keypair = await getSigningKeypair(signingKey);\n\t// Uint8Array wrapper is required - Buffer polyfill doesn't work with @atproto/crypto\n\tconst msgBytes = new Uint8Array(\n\t\tBuffer.from(`${headerB64}.${payloadB64}`, \"utf8\"),\n\t);\n\tconst sigBytes = new Uint8Array(Buffer.from(signatureB64, \"base64url\"));\n\n\tconst isValid = await verifySignature(keypair.did(), msgBytes, sigBytes, {\n\t\tallowMalleableSig: true,\n\t});\n\n\tif (!isValid) {\n\t\tthrow new Error(\"Invalid signature\");\n\t}\n\n\treturn payload;\n}\n","import { SignJWT, jwtVerify, type JWTPayload } from \"jose\";\nimport { compare } from \"bcryptjs\";\n\nconst ACCESS_TOKEN_LIFETIME = \"2h\";\nconst REFRESH_TOKEN_LIFETIME = \"90d\";\n\n/**\n * Create a secret key from string for HS256 signing\n */\nfunction createSecretKey(secret: string): Uint8Array {\n\treturn new TextEncoder().encode(secret);\n}\n\n/**\n * Create an access token (short-lived, 2 hours)\n */\nexport async function createAccessToken(\n\tjwtSecret: string,\n\tuserDid: string,\n\tserviceDid: string,\n): Promise<string> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\treturn new SignJWT({ scope: \"atproto\" })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"at+jwt\" })\n\t\t.setIssuedAt()\n\t\t.setIssuer(serviceDid)\n\t\t.setAudience(serviceDid)\n\t\t.setSubject(userDid)\n\t\t.setExpirationTime(ACCESS_TOKEN_LIFETIME)\n\t\t.sign(secret);\n}\n\n/**\n * Create a refresh token (long-lived, 90 days)\n */\nexport async function createRefreshToken(\n\tjwtSecret: string,\n\tuserDid: string,\n\tserviceDid: string,\n): Promise<string> {\n\tconst secret = createSecretKey(jwtSecret);\n\tconst jti = crypto.randomUUID();\n\n\treturn new SignJWT({ scope: \"com.atproto.refresh\", jti })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"refresh+jwt\" })\n\t\t.setIssuedAt()\n\t\t.setIssuer(serviceDid)\n\t\t.setAudience(serviceDid)\n\t\t.setSubject(userDid)\n\t\t.setExpirationTime(REFRESH_TOKEN_LIFETIME)\n\t\t.sign(secret);\n}\n\n/**\n * Verify an access token and return the payload\n */\nexport async function verifyAccessToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tconst { payload, protectedHeader } = await jwtVerify(token, secret, {\n\t\tissuer: serviceDid,\n\t\taudience: serviceDid,\n\t});\n\n\t// Check token type\n\tif (protectedHeader.typ !== \"at+jwt\") {\n\t\tthrow new Error(\"Invalid token type\");\n\t}\n\n\t// Check scope\n\tif (payload.scope !== \"atproto\") {\n\t\tthrow new Error(\"Invalid scope\");\n\t}\n\n\treturn payload;\n}\n\n/**\n * Verify a refresh token and return the payload\n */\nexport async function verifyRefreshToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tconst { payload, protectedHeader } = await jwtVerify(token, secret, {\n\t\tissuer: serviceDid,\n\t\taudience: serviceDid,\n\t});\n\n\t// Check token type\n\tif (protectedHeader.typ !== \"refresh+jwt\") {\n\t\tthrow new Error(\"Invalid token type\");\n\t}\n\n\t// Check scope\n\tif (payload.scope !== \"com.atproto.refresh\") {\n\t\tthrow new Error(\"Invalid scope\");\n\t}\n\n\t// Require token ID\n\tif (!payload.jti) {\n\t\tthrow new Error(\"Missing token ID\");\n\t}\n\n\treturn payload;\n}\n\n/**\n * Verify a password against a bcrypt hash\n */\nexport { compare as verifyPassword };\n","/**\n * OAuth 2.1 integration for the PDS\n *\n * Connects the @getcirrus/oauth-provider package with the PDS\n * by providing storage through Durable Objects and user authentication\n * through the existing session system.\n */\n\nimport { Hono } from \"hono\";\nimport { ATProtoOAuthProvider } from \"@getcirrus/oauth-provider\";\nimport type {\n\tOAuthStorage,\n\tAuthCodeData,\n\tTokenData,\n\tClientMetadata,\n\tPARData,\n} from \"@getcirrus/oauth-provider\";\nimport { compare } from \"bcryptjs\";\nimport type { PDSEnv } from \"./types\";\nimport type { AccountDurableObject } from \"./account-do\";\n\n/**\n * Proxy storage class that delegates to DO RPC methods\n *\n * This is needed because SqliteOAuthStorage instances contain a SQL connection\n * that can't be serialized across the DO RPC boundary. Instead, we delegate each\n * storage operation to individual RPC methods that pass only serializable data.\n */\nclass DOProxyOAuthStorage implements OAuthStorage {\n\tconstructor(private accountDO: DurableObjectStub<AccountDurableObject>) {}\n\n\tasync saveAuthCode(code: string, data: AuthCodeData): Promise<void> {\n\t\tawait this.accountDO.rpcSaveAuthCode(code, data);\n\t}\n\n\tasync getAuthCode(code: string): Promise<AuthCodeData | null> {\n\t\treturn this.accountDO.rpcGetAuthCode(code);\n\t}\n\n\tasync deleteAuthCode(code: string): Promise<void> {\n\t\tawait this.accountDO.rpcDeleteAuthCode(code);\n\t}\n\n\tasync saveTokens(data: TokenData): Promise<void> {\n\t\tawait this.accountDO.rpcSaveTokens(data);\n\t}\n\n\tasync getTokenByAccess(accessToken: string): Promise<TokenData | null> {\n\t\treturn this.accountDO.rpcGetTokenByAccess(accessToken);\n\t}\n\n\tasync getTokenByRefresh(refreshToken: string): Promise<TokenData | null> {\n\t\treturn this.accountDO.rpcGetTokenByRefresh(refreshToken);\n\t}\n\n\tasync revokeToken(accessToken: string): Promise<void> {\n\t\tawait this.accountDO.rpcRevokeToken(accessToken);\n\t}\n\n\tasync revokeAllTokens(sub: string): Promise<void> {\n\t\tawait this.accountDO.rpcRevokeAllTokens(sub);\n\t}\n\n\tasync saveClient(clientId: string, metadata: ClientMetadata): Promise<void> {\n\t\tawait this.accountDO.rpcSaveClient(clientId, metadata);\n\t}\n\n\tasync getClient(clientId: string): Promise<ClientMetadata | null> {\n\t\treturn this.accountDO.rpcGetClient(clientId);\n\t}\n\n\tasync savePAR(requestUri: string, data: PARData): Promise<void> {\n\t\tawait this.accountDO.rpcSavePAR(requestUri, data);\n\t}\n\n\tasync getPAR(requestUri: string): Promise<PARData | null> {\n\t\treturn this.accountDO.rpcGetPAR(requestUri);\n\t}\n\n\tasync deletePAR(requestUri: string): Promise<void> {\n\t\tawait this.accountDO.rpcDeletePAR(requestUri);\n\t}\n\n\tasync checkAndSaveNonce(nonce: string): Promise<boolean> {\n\t\treturn this.accountDO.rpcCheckAndSaveNonce(nonce);\n\t}\n}\n\n/**\n * Get the OAuth provider for the given environment\n * Exported for use in auth middleware for token verification\n */\nexport function getProvider(env: PDSEnv): ATProtoOAuthProvider {\n\tconst accountDO = getAccountDO(env);\n\tconst storage = new DOProxyOAuthStorage(accountDO);\n\tconst issuer = `https://${env.PDS_HOSTNAME}`;\n\n\treturn new ATProtoOAuthProvider({\n\t\tstorage,\n\t\tissuer,\n\t\tdpopRequired: true,\n\t\tenablePAR: true,\n\t\t// Password verification for authorization\n\t\tverifyUser: async (password: string) => {\n\t\t\tconst valid = await compare(password, env.PASSWORD_HASH);\n\t\t\tif (!valid) return null;\n\t\t\treturn {\n\t\t\t\tsub: env.DID,\n\t\t\t\thandle: env.HANDLE,\n\t\t\t};\n\t\t},\n\t});\n}\n\n// Module-level reference to getAccountDO for the exported getProvider function\nlet getAccountDO: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>;\n\n/**\n * Create OAuth routes for the PDS\n *\n * This creates a Hono sub-app with all OAuth endpoints:\n * - GET /.well-known/oauth-authorization-server - Server metadata\n * - GET /oauth/authorize - Authorization endpoint\n * - POST /oauth/authorize - Handle authorization consent\n * - POST /oauth/token - Token endpoint\n * - POST /oauth/par - Pushed Authorization Request\n *\n * @param accountDOGetter Function to get the account DO stub\n */\nexport function createOAuthApp(\n\taccountDOGetter: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>,\n) {\n\t// Store reference for the exported getProvider function\n\tgetAccountDO = accountDOGetter;\n\n\tconst oauth = new Hono<{ Bindings: PDSEnv }>();\n\n\t// OAuth server metadata\n\toauth.get(\"/.well-known/oauth-authorization-server\", (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleMetadata();\n\t});\n\n\t// Protected resource metadata (for token introspection discovery)\n\toauth.get(\"/.well-known/oauth-protected-resource\", (c) => {\n\t\tconst issuer = `https://${c.env.PDS_HOSTNAME}`;\n\t\treturn c.json({\n\t\t\tresource: issuer,\n\t\t\tauthorization_servers: [issuer],\n\t\t\tscopes_supported: [\n\t\t\t\t\"atproto\",\n\t\t\t\t\"transition:generic\",\n\t\t\t\t\"transition:chat.bsky\",\n\t\t\t],\n\t\t});\n\t});\n\n\t// Authorization endpoint\n\toauth.get(\"/oauth/authorize\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleAuthorize(c.req.raw);\n\t});\n\n\toauth.post(\"/oauth/authorize\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleAuthorize(c.req.raw);\n\t});\n\n\t// Token endpoint\n\toauth.post(\"/oauth/token\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handleToken(c.req.raw);\n\t});\n\n\t// Pushed Authorization Request endpoint\n\toauth.post(\"/oauth/par\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handlePAR(c.req.raw);\n\t});\n\n\t// Token revocation endpoint\n\toauth.post(\"/oauth/revoke\", async (c) => {\n\t\t// Parse the token from the request\n\t\t// RFC 7009 requires application/x-www-form-urlencoded, we also accept JSON\n\t\tconst contentType = c.req.header(\"Content-Type\") ?? \"\";\n\t\tlet token: string | undefined;\n\n\t\ttry {\n\t\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t\tconst json = await c.req.json();\n\t\t\t\ttoken = json.token;\n\t\t\t} else if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n\t\t\t\tconst body = await c.req.text();\n\t\t\t\tconst params = Object.fromEntries(new URLSearchParams(body).entries());\n\t\t\t\ttoken = params.token;\n\t\t\t} else if (!contentType) {\n\t\t\t\t// No Content-Type: treat as empty body (no token)\n\t\t\t\ttoken = undefined;\n\t\t\t} else {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"invalid_request\",\n\t\t\t\t\t\terror_description:\n\t\t\t\t\t\t\t\"Content-Type must be application/x-www-form-urlencoded (per RFC 7009) or application/json\",\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn c.json(\n\t\t\t\t{ error: \"invalid_request\", error_description: \"Failed to parse request body\" },\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\tif (!token) {\n\t\t\t// Per RFC 7009, return 200 even if no token provided\n\t\t\treturn c.json({});\n\t\t}\n\n\t\t// Try to revoke the token (RFC 7009 accepts both access and refresh tokens)\n\t\tconst accountDO = getAccountDO(c.env);\n\n\t\t// First try as access token\n\t\tawait accountDO.rpcRevokeToken(token);\n\n\t\t// Also check if it's a refresh token and revoke the associated access token\n\t\tconst tokenData = await accountDO.rpcGetTokenByRefresh(token);\n\t\tif (tokenData) {\n\t\t\tawait accountDO.rpcRevokeToken(tokenData.accessToken);\n\t\t}\n\n\t\t// Always return success (per RFC 7009)\n\t\treturn c.json({});\n\t});\n\n\treturn oauth;\n}\n","import type { Context, Next } from \"hono\";\nimport { verifyServiceJwt } from \"../service-auth\";\nimport { verifyAccessToken } from \"../session\";\nimport { getProvider } from \"../oauth\";\nimport type { PDSEnv } from \"../types\";\n\nexport interface AuthInfo {\n\tdid: string;\n\tscope: string;\n}\n\nexport type AuthVariables = {\n\tauth: AuthInfo;\n};\n\nexport async function requireAuth(\n\tc: Context<{ Bindings: PDSEnv; Variables: AuthVariables }>,\n\tnext: Next,\n): Promise<Response | void> {\n\tconst auth = c.req.header(\"Authorization\");\n\n\tif (!auth) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthMissing\",\n\t\t\t\tmessage: \"Authorization header required\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\t// Handle DPoP-bound OAuth tokens\n\tif (auth.startsWith(\"DPoP \")) {\n\t\tconst provider = getProvider(c.env);\n\n\t\t// Verify OAuth access token with DPoP proof\n\t\tconst tokenData = await provider.verifyAccessToken(c.req.raw);\n\t\tif (!tokenData) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid OAuth access token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\tc.set(\"auth\", { did: tokenData.sub, scope: tokenData.scope });\n\t\treturn next();\n\t}\n\n\t// Handle Bearer tokens (session JWTs, static token, service JWTs)\n\tif (!auth.startsWith(\"Bearer \")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthMissing\",\n\t\t\t\tmessage: \"Invalid authorization scheme\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\tconst token = auth.slice(7);\n\n\t// Try static token first (backwards compatibility)\n\tif (token === c.env.AUTH_TOKEN) {\n\t\tc.set(\"auth\", { did: c.env.DID, scope: \"atproto\" });\n\t\treturn next();\n\t}\n\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\t// Try session JWT verification (HS256, signed with JWT_SECRET)\n\t// Used by Bluesky app for normal operations (posts, likes, etc.)\n\ttry {\n\t\tconst payload = await verifyAccessToken(\n\t\t\ttoken,\n\t\t\tc.env.JWT_SECRET,\n\t\t\tserviceDid,\n\t\t);\n\n\t\t// Verify subject matches our DID\n\t\tif (payload.sub !== c.env.DID) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid access token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\t// Store auth info in context for downstream use\n\t\tc.set(\"auth\", { did: payload.sub, scope: payload.scope as string });\n\t\treturn next();\n\t} catch {\n\t\t// Session JWT verification failed, try service JWT\n\t}\n\n\t// Try service JWT verification (ES256K, signed with our signing key)\n\t// Used by external services (like video.bsky.app) calling back to our PDS\n\ttry {\n\t\tconst payload = await verifyServiceJwt(\n\t\t\ttoken,\n\t\t\tc.env.SIGNING_KEY,\n\t\t\tserviceDid, // audience should be our PDS\n\t\t\tc.env.DID, // issuer should be the user's DID\n\t\t);\n\n\t\t// Store auth info in context\n\t\tc.set(\"auth\", { did: payload.iss, scope: payload.lxm || \"atproto\" });\n\t\treturn next();\n\t} catch {\n\t\t// Service JWT verification also failed\n\t}\n\n\treturn c.json(\n\t\t{\n\t\t\terror: \"AuthenticationRequired\",\n\t\t\tmessage: \"Invalid authentication token\",\n\t\t},\n\t\t401,\n\t);\n}\n","/**\n * DID resolution for Cloudflare Workers\n *\n * We can't use @atproto/identity directly because it uses `redirect: \"error\"`\n * which Cloudflare Workers doesn't support. This is a simple implementation\n * that's compatible with Workers.\n */\n\nimport { check, didDocument, type DidDocument } from \"@atproto/common-web\";\nimport type { DidCache } from \"@atproto/identity\";\n\nconst PLC_DIRECTORY = \"https://plc.directory\";\nconst TIMEOUT_MS = 3000;\n\nexport interface DidResolverOpts {\n\tplcUrl?: string;\n\ttimeout?: number;\n\tdidCache?: DidCache;\n}\n\nexport class DidResolver {\n\tprivate plcUrl: string;\n\tprivate timeout: number;\n\tprivate cache?: DidCache;\n\n\tconstructor(opts: DidResolverOpts = {}) {\n\t\tthis.plcUrl = opts.plcUrl ?? PLC_DIRECTORY;\n\t\tthis.timeout = opts.timeout ?? TIMEOUT_MS;\n\t\tthis.cache = opts.didCache;\n\t}\n\n\tasync resolve(did: string): Promise<DidDocument | null> {\n\t\t// Check cache first\n\t\tif (this.cache) {\n\t\t\tconst cached = await this.cache.checkCache(did);\n\t\t\tif (cached && !cached.expired) {\n\t\t\t\t// Trigger background refresh if stale\n\t\t\t\tif (cached.stale) {\n\t\t\t\t\tthis.cache.refreshCache(did, () => this.resolveNoCache(did), cached);\n\t\t\t\t}\n\t\t\t\treturn cached.doc;\n\t\t\t}\n\t\t}\n\n\t\tconst doc = await this.resolveNoCache(did);\n\n\t\t// Update cache\n\t\tif (doc && this.cache) {\n\t\t\tawait this.cache.cacheDid(did, doc);\n\t\t} else if (!doc && this.cache) {\n\t\t\tawait this.cache.clearEntry(did);\n\t\t}\n\n\t\treturn doc;\n\t}\n\n\tprivate async resolveNoCache(did: string): Promise<DidDocument | null> {\n\t\tif (did.startsWith(\"did:web:\")) {\n\t\t\treturn this.resolveDidWeb(did);\n\t\t}\n\t\tif (did.startsWith(\"did:plc:\")) {\n\t\t\treturn this.resolveDidPlc(did);\n\t\t}\n\t\tthrow new Error(`Unsupported DID method: ${did}`);\n\t}\n\n\tprivate async resolveDidWeb(did: string): Promise<DidDocument | null> {\n\t\tconst parts = did.split(\":\").slice(2);\n\t\tif (parts.length === 0) {\n\t\t\tthrow new Error(`Invalid did:web format: ${did}`);\n\t\t}\n\n\t\t// Only support simple did:web without paths (like @atproto/identity)\n\t\tif (parts.length > 1) {\n\t\t\tthrow new Error(`Unsupported did:web with path: ${did}`);\n\t\t}\n\n\t\tconst domain = decodeURIComponent(parts[0]!);\n\t\tconst url = new URL(`https://${domain}/.well-known/did.json`);\n\n\t\t// Use http for localhost\n\t\tif (url.hostname === \"localhost\") {\n\t\t\turl.protocol = \"http:\";\n\t\t}\n\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url.toString(), {\n\t\t\t\tsignal: controller.signal,\n\t\t\t\tredirect: \"manual\", // Workers doesn't support \"error\"\n\t\t\t\theaders: { accept: \"application/did+ld+json,application/json\" },\n\t\t\t});\n\n\t\t\t// Check for redirect (we don't follow them for security)\n\t\t\tif (res.status >= 300 && res.status < 400) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (!res.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst doc = await res.json();\n\t\t\treturn this.validateDidDoc(did, doc);\n\t\t} finally {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t}\n\n\tprivate async resolveDidPlc(did: string): Promise<DidDocument | null> {\n\t\tconst url = new URL(`/${encodeURIComponent(did)}`, this.plcUrl);\n\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url.toString(), {\n\t\t\t\tsignal: controller.signal,\n\t\t\t\tredirect: \"manual\", // Workers doesn't support \"error\"\n\t\t\t\theaders: { accept: \"application/did+ld+json,application/json\" },\n\t\t\t});\n\n\t\t\t// Check for redirect (we don't follow them for security)\n\t\t\tif (res.status >= 300 && res.status < 400) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (res.status === 404) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (!res.ok) {\n\t\t\t\tthrow new Error(`PLC directory error: ${res.status} ${res.statusText}`);\n\t\t\t}\n\n\t\t\tconst doc = (await res.json()) as DidDocument;\n\t\t\treturn this.validateDidDoc(did, doc);\n\t\t} finally {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t}\n\n\tprivate validateDidDoc(did: string, doc: unknown): DidDocument | null {\n\t\tif (!check.is(doc, didDocument)) {\n\t\t\treturn null;\n\t\t}\n\t\tif (doc.id !== did) {\n\t\t\treturn null;\n\t\t}\n\t\treturn doc;\n\t}\n}\n","/**\n * DID cache using Cloudflare Workers Cache API\n */\n\nimport type { DidCache, CacheResult, DidDocument } from \"@atproto/identity\";\nimport { check, didDocument } from \"@atproto/common-web\";\nimport { waitUntil } from \"cloudflare:workers\";\n\nconst STALE_TTL = 60 * 60 * 1000; // 1 hour - serve from cache but refresh in background\nconst MAX_TTL = 24 * 60 * 60 * 1000; // 24 hours - must refresh\n\nexport class WorkersDidCache implements DidCache {\n\tprivate cache: Cache;\n\n\tconstructor() {\n\t\tthis.cache = caches.default;\n\t}\n\n\tprivate getCacheKey(did: string): string {\n\t\t// Use a stable URL format for cache keys\n\t\treturn `https://did-cache.internal/${encodeURIComponent(did)}`;\n\t}\n\n\tasync cacheDid(\n\t\tdid: string,\n\t\tdoc: DidDocument,\n\t\t_prevResult?: CacheResult,\n\t): Promise<void> {\n\t\tconst cacheKey = this.getCacheKey(did);\n\t\tconst response = new Response(JSON.stringify(doc), {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"Cache-Control\": \"max-age=86400\", // 24 hours\n\t\t\t\t\"X-Cached-At\": Date.now().toString(),\n\t\t\t},\n\t\t});\n\n\t\tawait this.cache.put(cacheKey, response);\n\t}\n\n\tasync checkCache(did: string): Promise<CacheResult | null> {\n\t\tconst cacheKey = this.getCacheKey(did);\n\t\tconst response = await this.cache.match(cacheKey);\n\n\t\tif (!response) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst cachedAt = parseInt(response.headers.get(\"X-Cached-At\") || \"0\", 10);\n\t\tconst now = Date.now();\n\t\tconst age = now - cachedAt;\n\n\t\tconst doc = await response.json();\n\n\t\t// Validate cached document schema\n\t\tif (!check.is(doc, didDocument) || doc.id !== did) {\n\t\t\tawait this.clearEntry(did);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tdid,\n\t\t\tdoc,\n\t\t\tupdatedAt: cachedAt,\n\t\t\tstale: age > STALE_TTL,\n\t\t\texpired: age > MAX_TTL,\n\t\t};\n\t}\n\n\tasync refreshCache(\n\t\tdid: string,\n\t\tgetDoc: () => Promise<DidDocument | null>,\n\t\t_prevResult?: CacheResult,\n\t): Promise<void> {\n\t\t// Background refresh using waitUntil to ensure it completes after response\n\t\twaitUntil(\n\t\t\tgetDoc().then((doc) => {\n\t\t\t\tif (doc) {\n\t\t\t\t\treturn this.cacheDid(did, doc);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tasync clearEntry(did: string): Promise<void> {\n\t\tconst cacheKey = this.getCacheKey(did);\n\t\tawait this.cache.delete(cacheKey);\n\t}\n\n\tasync clear(): Promise<void> {\n\t\t// Cache API doesn't have a clear-all method\n\t\t// Would need to track keys separately if needed\n\t\t// For now, entries will expire naturally\n\t}\n}\n","/**\n * XRPC service proxying with atproto-proxy header support\n * See: https://atproto.com/specs/xrpc#service-proxying\n */\n\nimport type { Context } from \"hono\";\nimport { DidResolver } from \"./did-resolver\";\nimport { getServiceEndpoint } from \"@atproto/common-web\";\nimport { createServiceJwt } from \"./service-auth\";\nimport { verifyAccessToken } from \"./session\";\nimport { getProvider } from \"./oauth\";\nimport type { PDSEnv } from \"./types\";\nimport type { Secp256k1Keypair } from \"@atproto/crypto\";\n\n/**\n * Parse atproto-proxy header value\n * Format: \"did:web:example.com#service_id\"\n * Returns: { did: \"did:web:example.com\", serviceId: \"service_id\" }\n */\nexport function parseProxyHeader(\n\theader: string,\n): { did: string; serviceId: string } | null {\n\tconst parts = header.split(\"#\");\n\tif (parts.length !== 2) {\n\t\treturn null;\n\t}\n\n\tconst [did, serviceId] = parts;\n\tif (!did?.startsWith(\"did:\") || !serviceId) {\n\t\treturn null;\n\t}\n\n\treturn { did, serviceId };\n}\n\n/**\n * Handle XRPC proxy requests\n * Routes requests to external services based on atproto-proxy header or lexicon namespace\n */\nexport async function handleXrpcProxy(\n\tc: Context<{ Bindings: PDSEnv }>,\n\tdidResolver: DidResolver,\n\tgetKeypair: () => Promise<Secp256k1Keypair>,\n): Promise<Response> {\n\t// Extract XRPC method name from path (e.g., \"app.bsky.feed.getTimeline\")\n\tconst url = new URL(c.req.url);\n\tconst lxm = url.pathname.replace(\"/xrpc/\", \"\");\n\n\t// Validate XRPC path to prevent path traversal\n\tif (lxm.includes(\"..\") || lxm.includes(\"//\")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Invalid XRPC method path\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Check for atproto-proxy header for explicit service routing\n\tconst proxyHeader = c.req.header(\"atproto-proxy\");\n\tlet audienceDid: string;\n\tlet targetUrl: URL;\n\n\tif (proxyHeader) {\n\t\t// Parse proxy header: \"did:web:example.com#service_id\"\n\t\tconst parsed = parseProxyHeader(proxyHeader);\n\t\tif (!parsed) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\tmessage: `Invalid atproto-proxy header format: ${proxyHeader}`,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// Resolve DID document to get service endpoint (with caching)\n\t\t\tconst didDoc = await didResolver.resolve(parsed.did);\n\t\t\tif (!didDoc) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\t\tmessage: `DID not found: ${parsed.did}`,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// getServiceEndpoint expects the ID to start with #\n\t\t\tconst serviceId = parsed.serviceId.startsWith(\"#\")\n\t\t\t\t? parsed.serviceId\n\t\t\t\t: `#${parsed.serviceId}`;\n\t\t\tconst endpoint = getServiceEndpoint(didDoc, { id: serviceId });\n\n\t\t\tif (!endpoint) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\t\tmessage: `Service not found in DID document: ${parsed.serviceId}`,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Use the resolved service endpoint\n\t\t\taudienceDid = parsed.did;\n\t\t\ttargetUrl = new URL(endpoint);\n\t\t\tif (targetUrl.protocol !== \"https:\") {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\t\tmessage: \"Proxy target must use HTTPS\",\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t\ttargetUrl.pathname = url.pathname;\n\t\t\ttargetUrl.search = url.search;\n\t\t} catch (err) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\t\tmessage: `Failed to resolve service: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\t} else {\n\t\t// Fallback: Route to Bluesky services based on lexicon namespace\n\t\t// These are well-known endpoints that don't require DID resolution\n\t\tconst isChat = lxm.startsWith(\"chat.bsky.\");\n\t\taudienceDid = isChat ? \"did:web:api.bsky.chat\" : \"did:web:api.bsky.app\";\n\t\tconst endpoint = isChat ? \"https://api.bsky.chat\" : \"https://api.bsky.app\";\n\n\t\t// Construct URL safely using URL constructor\n\t\ttargetUrl = new URL(`/xrpc/${lxm}${url.search}`, endpoint);\n\t}\n\n\t// Verify auth and create service JWT for target service\n\tlet headers: Record<string, string> = {};\n\tconst auth = c.req.header(\"Authorization\");\n\tlet userDid: string | undefined;\n\n\tif (auth?.startsWith(\"DPoP \")) {\n\t\t// Verify DPoP-bound OAuth access token\n\t\ttry {\n\t\t\tconst provider = getProvider(c.env);\n\t\t\tconst tokenData = await provider.verifyAccessToken(c.req.raw);\n\t\t\tif (tokenData) {\n\t\t\t\tuserDid = tokenData.sub;\n\t\t\t}\n\t\t} catch {\n\t\t\t// DPoP verification failed - continue without auth\n\t\t}\n\t} else if (auth?.startsWith(\"Bearer \")) {\n\t\tconst token = auth.slice(7);\n\t\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\t\ttry {\n\t\t\t// Check static token first\n\t\t\tif (token === c.env.AUTH_TOKEN) {\n\t\t\t\tuserDid = c.env.DID;\n\t\t\t} else {\n\t\t\t\t// Verify JWT\n\t\t\t\tconst payload = await verifyAccessToken(\n\t\t\t\t\ttoken,\n\t\t\t\t\tc.env.JWT_SECRET,\n\t\t\t\t\tserviceDid,\n\t\t\t\t);\n\t\t\t\tif (payload.sub) {\n\t\t\t\t\tuserDid = payload.sub;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Token verification failed - continue without auth\n\t\t}\n\t}\n\n\t// Create service JWT if user is authenticated\n\tif (userDid) {\n\t\ttry {\n\t\t\tconst keypair = await getKeypair();\n\t\t\tconst serviceJwt = await createServiceJwt({\n\t\t\t\tiss: userDid,\n\t\t\t\taud: audienceDid,\n\t\t\t\tlxm,\n\t\t\t\tkeypair,\n\t\t\t});\n\t\t\theaders[\"Authorization\"] = `Bearer ${serviceJwt}`;\n\t\t} catch {\n\t\t\t// Service JWT creation failed - forward without auth\n\t\t}\n\t}\n\n\t// Forward request with potentially replaced auth header\n\t// Use Headers object for case-insensitive handling\n\tconst forwardHeaders = new Headers(c.req.raw.headers);\n\n\t// Remove headers that shouldn't be forwarded (security/privacy)\n\tconst headersToRemove = [\n\t\t\"authorization\", // Replaced with service JWT\n\t\t\"atproto-proxy\", // Internal routing header\n\t\t\"host\", // Will be set by fetch\n\t\t\"connection\", // Connection-specific\n\t\t\"cookie\", // Privacy - don't leak cookies\n\t\t\"x-forwarded-for\", // Don't leak client IP\n\t\t\"x-real-ip\", // Don't leak client IP\n\t\t\"x-forwarded-proto\", // Internal\n\t\t\"x-forwarded-host\", // Internal\n\t];\n\n\tfor (const header of headersToRemove) {\n\t\tforwardHeaders.delete(header);\n\t}\n\n\t// Add service auth if we have it\n\tif (headers[\"Authorization\"]) {\n\t\tforwardHeaders.set(\"Authorization\", headers[\"Authorization\"]);\n\t}\n\n\tconst reqInit: RequestInit = {\n\t\tmethod: c.req.method,\n\t\theaders: forwardHeaders,\n\t};\n\n\t// Include body for non-GET requests\n\tif (c.req.method !== \"GET\" && c.req.method !== \"HEAD\") {\n\t\treqInit.body = c.req.raw.body;\n\t}\n\n\treturn fetch(targetUrl.toString(), reqInit);\n}\n","import type { Context } from \"hono\";\nimport { ensureValidDid } from \"@atproto/syntax\";\nimport type { AccountDurableObject } from \"../account-do.js\";\nimport type { AppEnv } from \"../types\";\n\nexport async function getRepo(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst carBytes = await accountDO.rpcGetRepoCar();\n\n\treturn new Response(carBytes, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/vnd.ipld.car\",\n\t\t\t\"Content-Length\": carBytes.length.toString(),\n\t\t},\n\t});\n}\n\nexport async function getRepoStatus(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst data = await accountDO.rpcGetRepoStatus();\n\n\treturn c.json({\n\t\tdid: data.did,\n\t\tactive: true,\n\t\tstatus: \"active\",\n\t\trev: data.rev,\n\t});\n}\n\nexport async function listRepos(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\t// Single-user PDS - just return our one repo\n\tconst data = await accountDO.rpcGetRepoStatus();\n\n\treturn c.json({\n\t\trepos: [\n\t\t\t{\n\t\t\t\tdid: data.did,\n\t\t\t\thead: data.head,\n\t\t\t\trev: data.rev,\n\t\t\t\tactive: true,\n\t\t\t},\n\t\t],\n\t});\n}\n\nexport async function listBlobs(\n\tc: Context<AppEnv>,\n\t_accountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\t// Check if blob storage is configured\n\tif (!c.env.BLOBS) {\n\t\t// No blobs configured, return empty list\n\t\treturn c.json({ cids: [] });\n\t}\n\n\t// List blobs from R2 with prefix\n\tconst prefix = `${did}/`;\n\tconst cursor = c.req.query(\"cursor\");\n\tconst limit = Math.min(Number(c.req.query(\"limit\")) || 500, 1000);\n\n\tconst listed = await c.env.BLOBS.list({\n\t\tprefix,\n\t\tlimit,\n\t\tcursor: cursor || undefined,\n\t});\n\n\t// Extract CIDs from keys (keys are \"${did}/${cid}\")\n\tconst cids = listed.objects.map((obj) => obj.key.slice(prefix.length));\n\n\tconst result: { cids: string[]; cursor?: string } = { cids };\n\tif (listed.truncated && listed.cursor) {\n\t\tresult.cursor = listed.cursor;\n\t}\n\n\treturn c.json(result);\n}\n\nexport async function getBlocks(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\tconst cidsParam = c.req.queries(\"cids\");\n\n\tif (!did) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: did\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (!cidsParam || cidsParam.length === 0) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: cids\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst carBytes = await accountDO.rpcGetBlocks(cidsParam);\n\n\treturn new Response(carBytes, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/vnd.ipld.car\",\n\t\t\t\"Content-Length\": carBytes.length.toString(),\n\t\t},\n\t});\n}\n\nexport async function getBlob(\n\tc: Context<AppEnv>,\n\t_accountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\tconst cid = c.req.query(\"cid\");\n\n\tif (!did || !cid) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: did, cid\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(did);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (did !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found for DID: ${did}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\t// Check if blob storage is configured\n\tif (!c.env.BLOBS) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"ServiceUnavailable\",\n\t\t\t\tmessage: \"Blob storage is not configured\",\n\t\t\t},\n\t\t\t503,\n\t\t);\n\t}\n\n\t// Access R2 directly (R2ObjectBody can't be serialized across RPC)\n\tconst key = `${did}/${cid}`;\n\tconst blob = await c.env.BLOBS.get(key);\n\n\tif (!blob) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"BlobNotFound\",\n\t\t\t\tmessage: `Blob not found: ${cid}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\treturn new Response(blob.body, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\":\n\t\t\t\tblob.httpMetadata?.contentType || \"application/octet-stream\",\n\t\t\t\"Content-Length\": blob.size.toString(),\n\t\t},\n\t});\n}\n","","","","","","","","","","","","","","","","","","","","","","","import { Lexicons, jsonToLex, type LexiconDoc } from \"@atproto/lexicon\";\n\n/**\n * Record validator for AT Protocol records.\n *\n * Validates records against official Bluesky lexicon schemas.\n * Uses optimistic validation strategy:\n * - If a lexicon schema is loaded for the collection, validate the record\n * - If no schema is loaded, allow the record (fail-open)\n *\n * This allows the PDS to accept records for new or unknown collection types\n * while still validating known types when schemas are available.\n */\nexport class RecordValidator {\n\tprivate lex: Lexicons;\n\tprivate strictMode: boolean;\n\n\tconstructor(options: { strict?: boolean; lexicons?: Lexicons } = {}) {\n\t\tthis.lex = options.lexicons ?? new Lexicons();\n\t\tthis.strictMode = options.strict ?? false;\n\n\t\t// Load official Bluesky schemas\n\t\tthis.loadBlueskySchemas();\n\t}\n\n\t/**\n\t * Load official Bluesky lexicon schemas from vendored JSON files.\n\t * Uses Vite's glob import to automatically load all schema files.\n\t */\n\tprivate loadBlueskySchemas(): void {\n\t\t// Import all lexicon JSON files using Vite's glob import\n\t\tconst schemas = import.meta.glob<{ default: LexiconDoc }>(\n\t\t\t\"./lexicons/*.json\",\n\t\t\t{ eager: true },\n\t\t);\n\n\t\t// Load each schema into the Lexicons instance\n\t\tfor (const schema of Object.values(schemas)) {\n\t\t\tthis.lex.add(schema.default);\n\t\t}\n\t}\n\n\t/**\n\t * Validate a record against its lexicon schema.\n\t *\n\t * @param collection - The NSID of the record type (e.g., \"app.bsky.feed.post\")\n\t * @param record - The record object to validate\n\t * @throws {Error} If validation fails and schema is loaded\n\t */\n\tvalidateRecord(collection: string, record: unknown): void {\n\t\t// Check if we have a schema for this collection\n\t\tconst hasSchema = this.hasSchema(collection);\n\n\t\tif (!hasSchema) {\n\t\t\t// Optimistic validation: if we don't have the schema, allow it\n\t\t\tif (this.strictMode) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No lexicon schema loaded for collection: ${collection}. Enable optimistic validation or add the schema.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// In non-strict mode, we allow unknown schemas\n\t\t\treturn;\n\t\t}\n\n\t\t// Convert JSON to lexicon format (handles $link -> CID, blob -> BlobRef)\n\t\tconst lexRecord = jsonToLex(record);\n\n\t\t// We have a schema, so validate against it\n\t\ttry {\n\t\t\tthis.lex.assertValidRecord(collection, lexRecord);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new Error(\n\t\t\t\t`Lexicon validation failed for ${collection}: ${message}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Check if a schema is loaded for a collection.\n\t */\n\thasSchema(collection: string): boolean {\n\t\ttry {\n\t\t\t// Try to get the schema - if it exists, this won't throw\n\t\t\tthis.lex.getDefOrThrow(collection);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Add a lexicon schema to the validator.\n\t *\n\t * @param doc - The lexicon document to add\n\t *\n\t * @example\n\t * ```ts\n\t * validator.addSchema({\n\t * lexicon: 1,\n\t * id: \"com.example.post\",\n\t * defs: { ... }\n\t * })\n\t * ```\n\t */\n\taddSchema(doc: any): void {\n\t\tthis.lex.add(doc);\n\t}\n\n\t/**\n\t * Get list of all loaded schema NSIDs.\n\t */\n\tgetLoadedSchemas(): string[] {\n\t\t// Convert the Lexicons iterable to an array and extract IDs\n\t\treturn Array.from(this.lex).map((doc) => doc.id);\n\t}\n\n\t/**\n\t * Get the underlying Lexicons instance for advanced usage.\n\t */\n\tgetLexicons(): Lexicons {\n\t\treturn this.lex;\n\t}\n}\n\n/**\n * Shared validator instance (singleton pattern).\n * Uses optimistic validation by default (strict: false).\n *\n * Automatically loads all schemas from ./lexicons/*.json\n *\n * Additional schemas can be added:\n * ```ts\n * import { validator } from './validation'\n * validator.addSchema(myCustomSchema)\n * ```\n */\nexport const validator = new RecordValidator({ strict: false });\n","import type { Context } from \"hono\";\nimport { AtUri, ensureValidDid } from \"@atproto/syntax\";\nimport { AccountDurableObject } from \"../account-do\";\nimport type { AppEnv, AuthedAppEnv } from \"../types\";\nimport { validator } from \"../validation\";\n\nfunction invalidRecordError(\n\tc: Context<AuthedAppEnv>,\n\terr: unknown,\n\tprefix?: string,\n): Response {\n\tconst message = err instanceof Error ? err.message : String(err);\n\treturn c.json(\n\t\t{\n\t\t\terror: \"InvalidRecord\",\n\t\t\tmessage: prefix ? `${prefix}: ${message}` : message,\n\t\t},\n\t\t400,\n\t);\n}\n\n/**\n * Check if an error is an AccountDeactivated error and return appropriate HTTP 403 response.\n * @param c - Hono context for creating the response\n * @param err - The error to check (expected format: \"AccountDeactivated: <message>\")\n * @returns HTTP 403 Response with AccountDeactivated error type, or null if not a deactivation error\n */\nfunction checkAccountDeactivatedError(\n\tc: Context<AuthedAppEnv>,\n\terr: unknown,\n): Response | null {\n\tconst message = err instanceof Error ? err.message : String(err);\n\tif (message.startsWith(\"AccountDeactivated:\")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AccountDeactivated\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"Account is deactivated. Call activateAccount to enable writes.\",\n\t\t\t},\n\t\t\t403,\n\t\t);\n\t}\n\treturn null;\n}\n\nexport async function describeRepo(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst repo = c.req.query(\"repo\");\n\n\tif (!repo) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: repo\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(repo);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found: ${repo}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst data = await accountDO.rpcDescribeRepo();\n\n\treturn c.json({\n\t\tdid: c.env.DID,\n\t\thandle: c.env.HANDLE,\n\t\tdidDoc: {\n\t\t\t\"@context\": [\"https://www.w3.org/ns/did/v1\"],\n\t\t\tid: c.env.DID,\n\t\t\talsoKnownAs: [`at://${c.env.HANDLE}`],\n\t\t\tverificationMethod: [\n\t\t\t\t{\n\t\t\t\t\tid: `${c.env.DID}#atproto`,\n\t\t\t\t\ttype: \"Multikey\",\n\t\t\t\t\tcontroller: c.env.DID,\n\t\t\t\t\tpublicKeyMultibase: c.env.SIGNING_KEY_PUBLIC,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tcollections: data.collections,\n\t\thandleIsCorrect: true,\n\t});\n}\n\nexport async function getRecord(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst repo = c.req.query(\"repo\");\n\tconst collection = c.req.query(\"collection\");\n\tconst rkey = c.req.query(\"rkey\");\n\n\tif (!repo || !collection || !rkey) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, rkey\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(repo);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found: ${repo}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst result = await accountDO.rpcGetRecord(collection, rkey);\n\n\tif (!result) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RecordNotFound\",\n\t\t\t\tmessage: `Record not found: ${collection}/${rkey}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\treturn c.json({\n\t\turi: AtUri.make(repo, collection, rkey).toString(),\n\t\tcid: result.cid,\n\t\tvalue: result.record,\n\t});\n}\n\nexport async function listRecords(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst repo = c.req.query(\"repo\");\n\tconst collection = c.req.query(\"collection\");\n\tconst limitStr = c.req.query(\"limit\");\n\tconst cursor = c.req.query(\"cursor\");\n\tconst reverseStr = c.req.query(\"reverse\");\n\n\tif (!repo || !collection) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\ttry {\n\t\tensureValidDid(repo);\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: `Invalid DID format: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoNotFound\",\n\t\t\t\tmessage: `Repository not found: ${repo}`,\n\t\t\t},\n\t\t\t404,\n\t\t);\n\t}\n\n\tconst limit = Math.min(limitStr ? Number.parseInt(limitStr, 10) : 50, 100);\n\tconst reverse = reverseStr === \"true\";\n\n\tconst result = await accountDO.rpcListRecords(collection, {\n\t\tlimit,\n\t\tcursor,\n\t\treverse,\n\t});\n\n\treturn c.json(result);\n}\n\nexport async function createRecord(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, collection, rkey, record } = body;\n\n\tif (!repo || !collection || !record) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, record\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate record against lexicon schema\n\ttry {\n\t\tvalidator.validateRecord(collection, record);\n\t} catch (err) {\n\t\treturn invalidRecordError(c, err);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcCreateRecord(collection, rkey, record);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\tthrow err;\n\t}\n}\n\nexport async function deleteRecord(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, collection, rkey } = body;\n\n\tif (!repo || !collection || !rkey) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, rkey\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcDeleteRecord(collection, rkey);\n\n\t\tif (!result) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"RecordNotFound\",\n\t\t\t\t\tmessage: `Record not found: ${collection}/${rkey}`,\n\t\t\t\t},\n\t\t\t\t404,\n\t\t\t);\n\t\t}\n\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\tthrow err;\n\t}\n}\n\nexport async function putRecord(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, collection, rkey, record } = body;\n\n\tif (!repo || !collection || !rkey || !record) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, collection, rkey, record\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate record against lexicon schema\n\ttry {\n\t\tvalidator.validateRecord(collection, record);\n\t} catch (err) {\n\t\treturn invalidRecordError(c, err);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcPutRecord(collection, rkey, record);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n}\n\nexport async function applyWrites(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json();\n\tconst { repo, writes } = body;\n\n\tif (!repo || !writes || !Array.isArray(writes)) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameters: repo, writes\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (repo !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\tmessage: `Invalid repository: ${repo}`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (writes.length > 200) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Too many writes. Max: 200\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate all records in create and update operations\n\tfor (let i = 0; i < writes.length; i++) {\n\t\tconst write = writes[i];\n\t\tif (\n\t\t\twrite.$type === \"com.atproto.repo.applyWrites#create\" ||\n\t\t\twrite.$type === \"com.atproto.repo.applyWrites#update\"\n\t\t) {\n\t\t\ttry {\n\t\t\t\tvalidator.validateRecord(write.collection, write.value);\n\t\t\t} catch (err) {\n\t\t\t\treturn invalidRecordError(c, err, `Write ${i}`);\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcApplyWrites(writes);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst deactivatedError = checkAccountDeactivatedError(c, err);\n\t\tif (deactivatedError) return deactivatedError;\n\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n}\n\nexport async function uploadBlob(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst contentType =\n\t\tc.req.header(\"Content-Type\") || \"application/octet-stream\";\n\tconst bytes = new Uint8Array(await c.req.arrayBuffer());\n\n\t// Size limit check (5MB)\n\tconst MAX_BLOB_SIZE = 5 * 1024 * 1024;\n\tif (bytes.length > MAX_BLOB_SIZE) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"BlobTooLarge\",\n\t\t\t\tmessage: `Blob size ${bytes.length} exceeds maximum of ${MAX_BLOB_SIZE} bytes`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\ttry {\n\t\tconst blobRef = await accountDO.rpcUploadBlob(bytes, contentType);\n\t\treturn c.json({ blob: blobRef });\n\t} catch (err) {\n\t\tif (\n\t\t\terr instanceof Error &&\n\t\t\terr.message.includes(\"Blob storage not configured\")\n\t\t) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ServiceUnavailable\",\n\t\t\t\t\tmessage: \"Blob storage is not configured\",\n\t\t\t\t},\n\t\t\t\t503,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nexport async function importRepo(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst contentType = c.req.header(\"Content-Type\");\n\n\t// Verify content type\n\tif (contentType !== \"application/vnd.ipld.car\") {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"Content-Type must be application/vnd.ipld.car for repository import\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Get CAR file bytes\n\tconst carBytes = new Uint8Array(await c.req.arrayBuffer());\n\n\tif (carBytes.length === 0) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Empty CAR file\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Size limit check (100MB for repo imports)\n\tconst MAX_CAR_SIZE = 100 * 1024 * 1024;\n\tif (carBytes.length > MAX_CAR_SIZE) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"RepoTooLarge\",\n\t\t\t\tmessage: `Repository size ${carBytes.length} exceeds maximum of ${MAX_CAR_SIZE} bytes`,\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\ttry {\n\t\tconst result = await accountDO.rpcImportRepo(carBytes);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tif (err instanceof Error) {\n\t\t\tif (err.message.includes(\"already exists\")) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"RepoAlreadyExists\",\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"Repository already exists. Cannot import over existing data.\",\n\t\t\t\t\t},\n\t\t\t\t\t409,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (err.message.includes(\"DID mismatch\")) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\t\t\tmessage: err.message,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (\n\t\t\t\terr.message.includes(\"no roots\") ||\n\t\t\t\terr.message.includes(\"no blocks\") ||\n\t\t\t\terr.message.includes(\"Invalid root\")\n\t\t\t) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"InvalidRepo\",\n\t\t\t\t\t\tmessage: `Invalid CAR file: ${err.message}`,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/**\n * List blobs that are referenced in records but not yet imported.\n * Used during migration to track which blobs still need to be uploaded.\n */\nexport async function listMissingBlobs(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst limitStr = c.req.query(\"limit\");\n\tconst cursor = c.req.query(\"cursor\");\n\n\tconst limit = limitStr ? Math.min(Number.parseInt(limitStr, 10), 500) : 500;\n\n\tconst result = await accountDO.rpcListMissingBlobs(\n\t\tlimit,\n\t\tcursor || undefined,\n\t);\n\n\treturn c.json(result);\n}\n","import type { Context } from \"hono\";\nimport type { AccountDurableObject } from \"../account-do\";\nimport { createServiceJwt, getSigningKeypair } from \"../service-auth\";\nimport {\n\tcreateAccessToken,\n\tcreateRefreshToken,\n\tverifyPassword,\n\tverifyAccessToken,\n\tverifyRefreshToken,\n} from \"../session\";\nimport type { AppEnv, AuthedAppEnv } from \"../types\";\n\nexport async function describeServer(c: Context<AppEnv>): Promise<Response> {\n\treturn c.json({\n\t\tdid: c.env.DID,\n\t\tavailableUserDomains: [],\n\t\tinviteCodeRequired: false,\n\t});\n}\n\n/**\n * Create a new session (login)\n */\nexport async function createSession(c: Context<AppEnv>): Promise<Response> {\n\tconst body = await c.req.json<{\n\t\tidentifier: string;\n\t\tpassword: string;\n\t}>();\n\n\tconst { identifier, password } = body;\n\n\tif (!identifier || !password) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing identifier or password\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Check identifier matches handle or DID\n\tif (identifier !== c.env.HANDLE && identifier !== c.env.DID) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Invalid identifier or password\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\t// Verify password\n\tconst passwordValid = await verifyPassword(password, c.env.PASSWORD_HASH);\n\tif (!passwordValid) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Invalid identifier or password\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\t// Create tokens\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\tconst accessJwt = await createAccessToken(\n\t\tc.env.JWT_SECRET,\n\t\tc.env.DID,\n\t\tserviceDid,\n\t);\n\tconst refreshJwt = await createRefreshToken(\n\t\tc.env.JWT_SECRET,\n\t\tc.env.DID,\n\t\tserviceDid,\n\t);\n\n\treturn c.json({\n\t\taccessJwt,\n\t\trefreshJwt,\n\t\thandle: c.env.HANDLE,\n\t\tdid: c.env.DID,\n\t\tactive: true,\n\t});\n}\n\n/**\n * Refresh a session\n */\nexport async function refreshSession(c: Context<AppEnv>): Promise<Response> {\n\tconst authHeader = c.req.header(\"Authorization\");\n\n\tif (!authHeader?.startsWith(\"Bearer \")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Refresh token required\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\tconst token = authHeader.slice(7);\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\ttry {\n\t\tconst payload = await verifyRefreshToken(\n\t\t\ttoken,\n\t\t\tc.env.JWT_SECRET,\n\t\t\tserviceDid,\n\t\t);\n\n\t\t// Verify the subject matches our DID\n\t\tif (payload.sub !== c.env.DID) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid refresh token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\t// Create new tokens\n\t\tconst accessJwt = await createAccessToken(\n\t\t\tc.env.JWT_SECRET,\n\t\t\tc.env.DID,\n\t\t\tserviceDid,\n\t\t);\n\t\tconst refreshJwt = await createRefreshToken(\n\t\t\tc.env.JWT_SECRET,\n\t\t\tc.env.DID,\n\t\t\tserviceDid,\n\t\t);\n\n\t\treturn c.json({\n\t\t\taccessJwt,\n\t\t\trefreshJwt,\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Invalid refresh token\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n}\n\n/**\n * Get current session info\n */\nexport async function getSession(c: Context<AppEnv>): Promise<Response> {\n\tconst authHeader = c.req.header(\"Authorization\");\n\n\tif (!authHeader?.startsWith(\"Bearer \")) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\tmessage: \"Access token required\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n\n\tconst token = authHeader.slice(7);\n\tconst serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;\n\n\t// First try static token\n\tif (token === c.env.AUTH_TOKEN) {\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\tactive: true,\n\t\t});\n\t}\n\n\t// Try JWT\n\ttry {\n\t\tconst payload = await verifyAccessToken(\n\t\t\ttoken,\n\t\t\tc.env.JWT_SECRET,\n\t\t\tserviceDid,\n\t\t);\n\n\t\tif (payload.sub !== c.env.DID) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AuthenticationRequired\",\n\t\t\t\t\tmessage: \"Invalid access token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidToken\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Invalid access token\",\n\t\t\t},\n\t\t\t401,\n\t\t);\n\t}\n}\n\n/**\n * Delete current session (logout)\n */\nexport async function deleteSession(c: Context<AppEnv>): Promise<Response> {\n\t// For a single-user PDS with stateless JWTs, we don't need to do anything\n\t// The client just needs to delete its stored tokens\n\t// In a full implementation, we'd revoke the refresh token\n\treturn c.json({});\n}\n\n/**\n * Get account status - used for migration checks and progress tracking\n */\nexport async function getAccountStatus(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\t// Check if repo exists and get activation state\n\t\tconst status = await accountDO.rpcGetRepoStatus();\n\t\tconst active = await accountDO.rpcGetActive();\n\n\t\t// Get counts for migration progress tracking\n\t\tconst [repoBlocks, indexedRecords, expectedBlobs, importedBlobs] =\n\t\t\tawait Promise.all([\n\t\t\t\taccountDO.rpcCountBlocks(),\n\t\t\t\taccountDO.rpcCountRecords(),\n\t\t\t\taccountDO.rpcCountExpectedBlobs(),\n\t\t\t\taccountDO.rpcCountImportedBlobs(),\n\t\t\t]);\n\n\t\treturn c.json({\n\t\t\tactive: active,\n\t\t\tvalidDid: true,\n\t\t\trepoCommit: status.head,\n\t\t\trepoRev: status.rev,\n\t\t\trepoBlocks,\n\t\t\tindexedRecords,\n\t\t\tprivateStateValues: null,\n\t\t\texpectedBlobs,\n\t\t\timportedBlobs,\n\t\t});\n\t} catch (err) {\n\t\t// If repo doesn't exist yet, return empty status\n\t\treturn c.json({\n\t\t\tactive: false,\n\t\t\tvalidDid: true,\n\t\t\trepoCommit: null,\n\t\t\trepoRev: null,\n\t\t\trepoBlocks: 0,\n\t\t\tindexedRecords: 0,\n\t\t\tprivateStateValues: null,\n\t\t\texpectedBlobs: 0,\n\t\t\timportedBlobs: 0,\n\t\t});\n\t}\n}\n\n/**\n * Get a service auth token for communicating with external services.\n * Used by clients to get JWTs for services like video.bsky.app.\n */\nexport async function getServiceAuth(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\tconst aud = c.req.query(\"aud\");\n\tconst lxm = c.req.query(\"lxm\") || null;\n\n\tif (!aud) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: aud\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Create service JWT for the requested audience\n\tconst keypair = await getSigningKeypair(c.env.SIGNING_KEY);\n\tconst token = await createServiceJwt({\n\t\tiss: c.env.DID,\n\t\taud,\n\t\tlxm,\n\t\tkeypair,\n\t});\n\n\treturn c.json({ token });\n}\n\n/**\n * Activate account - enables writes and firehose events\n */\nexport async function activateAccount(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\tawait accountDO.rpcActivateAccount();\n\t\treturn c.json({ success: true });\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Unknown error\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n\n/**\n * Deactivate account - disables writes while keeping reads available\n */\nexport async function deactivateAccount(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\tawait accountDO.rpcDeactivateAccount();\n\t\treturn c.json({ success: true });\n\t} catch (err) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: err instanceof Error ? err.message : \"Unknown error\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n\n/**\n * Reset migration state - clears imported repo and blob tracking.\n * Only works on deactivated accounts.\n */\nexport async function resetMigration(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\ttry {\n\t\tconst result = await accountDO.rpcResetMigration();\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"Unknown error\";\n\n\t\t// Check for specific error types\n\t\tif (message.includes(\"AccountActive\")) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"AccountActive\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Cannot reset migration on an active account. Deactivate first.\",\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage,\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n","","// Public API\nexport { AccountDurableObject } from \"./account-do\";\nexport type { PDSEnv } from \"./types\";\n\nimport { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { env as _env } from \"cloudflare:workers\";\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { ensureValidDid, ensureValidHandle } from \"@atproto/syntax\";\nimport { requireAuth } from \"./middleware/auth\";\nimport { DidResolver } from \"./did-resolver\";\nimport { WorkersDidCache } from \"./did-cache\";\nimport { handleXrpcProxy } from \"./xrpc-proxy\";\nimport { createOAuthApp } from \"./oauth\";\nimport * as sync from \"./xrpc/sync\";\nimport * as repo from \"./xrpc/repo\";\nimport * as server from \"./xrpc/server\";\nimport type { PDSEnv } from \"./types\";\n\nimport { version } from \"../package.json\" with { type: \"json\" };\n\n// Cast env to PDSEnv for type safety\nconst env = _env as PDSEnv;\n\n// Validate required environment variables at module load\nconst required = [\n\t\"DID\",\n\t\"HANDLE\",\n\t\"PDS_HOSTNAME\",\n\t\"AUTH_TOKEN\",\n\t\"SIGNING_KEY\",\n\t\"SIGNING_KEY_PUBLIC\",\n\t\"JWT_SECRET\",\n\t\"PASSWORD_HASH\",\n] as const;\n\nfor (const key of required) {\n\tif (!env[key]) {\n\t\tthrow new Error(`Missing required environment variable: ${key}`);\n\t}\n}\n\n// Validate DID and handle formats\ntry {\n\tensureValidDid(env.DID);\n\tensureValidHandle(env.HANDLE);\n} catch (err) {\n\tthrow new Error(\n\t\t`Invalid DID or handle: ${err instanceof Error ? err.message : String(err)}`,\n\t);\n}\n\nconst didResolver = new DidResolver({\n\tdidCache: new WorkersDidCache(),\n\ttimeout: 3000, // 3 second timeout for DID resolution\n\tplcUrl: \"https://plc.directory\",\n});\n\n// Lazy-loaded keypair for service auth\nlet keypairPromise: Promise<Secp256k1Keypair> | null = null;\nfunction getKeypair(): Promise<Secp256k1Keypair> {\n\tif (!keypairPromise) {\n\t\tkeypairPromise = Secp256k1Keypair.import(env.SIGNING_KEY);\n\t}\n\treturn keypairPromise;\n}\n\nconst app = new Hono<{ Bindings: PDSEnv }>();\n\n// CORS middleware for all routes\napp.use(\n\t\"*\",\n\tcors({\n\t\torigin: \"*\",\n\t\tallowMethods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n\t\tallowHeaders: [\"*\"],\n\t\texposeHeaders: [\"Content-Type\"],\n\t\tmaxAge: 86400,\n\t}),\n);\n\n// Helper to get Account DO stub\nfunction getAccountDO(env: PDSEnv) {\n\tconst id = env.ACCOUNT.idFromName(\"account\");\n\treturn env.ACCOUNT.get(id);\n}\n\n// DID document for did:web resolution\napp.get(\"/.well-known/did.json\", (c) => {\n\tconst didDocument = {\n\t\t\"@context\": [\n\t\t\t\"https://www.w3.org/ns/did/v1\",\n\t\t\t\"https://w3id.org/security/multikey/v1\",\n\t\t\t\"https://w3id.org/security/suites/secp256k1-2019/v1\",\n\t\t],\n\t\tid: c.env.DID,\n\t\talsoKnownAs: [`at://${c.env.HANDLE}`],\n\t\tverificationMethod: [\n\t\t\t{\n\t\t\t\tid: `${c.env.DID}#atproto`,\n\t\t\t\ttype: \"Multikey\",\n\t\t\t\tcontroller: c.env.DID,\n\t\t\t\tpublicKeyMultibase: c.env.SIGNING_KEY_PUBLIC,\n\t\t\t},\n\t\t],\n\t\tservice: [\n\t\t\t{\n\t\t\t\tid: \"#atproto_pds\",\n\t\t\t\ttype: \"AtprotoPersonalDataServer\",\n\t\t\t\tserviceEndpoint: `https://${c.env.PDS_HOSTNAME}`,\n\t\t\t},\n\t\t],\n\t};\n\treturn c.json(didDocument);\n});\n\n// Handle verification for AT Protocol\n// Only served if handle matches PDS hostname\napp.get(\"/.well-known/atproto-did\", (c) => {\n\tif (c.env.HANDLE !== c.env.PDS_HOSTNAME) {\n\t\treturn c.notFound();\n\t}\n\treturn new Response(c.env.DID, {\n\t\theaders: { \"Content-Type\": \"text/plain\" },\n\t});\n});\n\n// Health check\napp.get(\"/health\", (c) =>\n\tc.json({\n\t\tstatus: \"ok\",\n\t\tversion,\n\t}),\n);\n\n// Homepage\napp.get(\"/\", (c) => {\n\tconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>☁️</title>\n<style>\n* { margin: 0; padding: 0; box-sizing: border-box; }\nbody {\n\tmin-height: 100vh;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\talign-items: center;\n\tfont-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;\n\tbackground: #f0f0f0;\n\tcolor: #000;\n\tpadding: 2rem;\n}\n.cloud { font-size: clamp(4rem, 15vw, 10rem); line-height: 1; }\n.name { font-size: clamp(1.5rem, 5vw, 3rem); font-weight: 700; letter-spacing: 0.2em; margin: 1rem 0; }\n.what { font-size: clamp(0.8rem, 2vw, 1rem); color: #666; max-width: 300px; text-align: center; }\n.handle { font-size: clamp(0.9rem, 2.5vw, 1.2rem); margin-top: 2rem; padding: 0.5rem 1rem; border: 2px solid #000; }\n:is(.handle, .name) a { color: inherit; text-decoration: none; }\n:is(.handle, .name) a:hover { text-decoration: underline; }\n.version { position: fixed; bottom: 1rem; right: 1rem; font-size: 0.7rem; color: #999; }\n</style>\n</head>\n<body>\n<div class=\"cloud\">☁️</div>\n<div class=\"name\"><a href=\"https://github.com/ascorbic/cirrus\">CIRRUS</a></div>\n<div class=\"what\">a personal data server for the atmosphere</div>\n<div class=\"handle\"><a href=\"https://bsky.app/profile/${c.env.HANDLE}\" target=\"_blank\">@${c.env.HANDLE}</a></div>\n<div class=\"version\">v${version}</div>\n</body>\n</html>`;\n\treturn c.html(html);\n});\n\n// Sync endpoints (federation)\napp.get(\"/xrpc/com.atproto.sync.getRepo\", (c) =>\n\tsync.getRepo(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.getRepoStatus\", (c) =>\n\tsync.getRepoStatus(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.getBlocks\", (c) =>\n\tsync.getBlocks(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.getBlob\", (c) =>\n\tsync.getBlob(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.listRepos\", (c) =>\n\tsync.listRepos(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.sync.listBlobs\", (c) =>\n\tsync.listBlobs(c, getAccountDO(c.env)),\n);\n\n// WebSocket firehose\napp.get(\"/xrpc/com.atproto.sync.subscribeRepos\", async (c) => {\n\tconst upgradeHeader = c.req.header(\"Upgrade\");\n\tif (upgradeHeader !== \"websocket\") {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Expected WebSocket upgrade\" },\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Use fetch() instead of RPC to avoid WebSocket serialization error\n\tconst accountDO = getAccountDO(c.env);\n\treturn accountDO.fetch(c.req.raw);\n});\n\n// Repository operations\napp.get(\"/xrpc/com.atproto.repo.describeRepo\", (c) =>\n\trepo.describeRepo(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.repo.getRecord\", (c) =>\n\trepo.getRecord(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.repo.listRecords\", (c) =>\n\trepo.listRecords(c, getAccountDO(c.env)),\n);\n\n// Write operations require authentication\napp.post(\"/xrpc/com.atproto.repo.createRecord\", requireAuth, (c) =>\n\trepo.createRecord(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.deleteRecord\", requireAuth, (c) =>\n\trepo.deleteRecord(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.uploadBlob\", requireAuth, (c) =>\n\trepo.uploadBlob(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.applyWrites\", requireAuth, (c) =>\n\trepo.applyWrites(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.putRecord\", requireAuth, (c) =>\n\trepo.putRecord(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.repo.importRepo\", requireAuth, (c) =>\n\trepo.importRepo(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.repo.listMissingBlobs\", requireAuth, (c) =>\n\trepo.listMissingBlobs(c, getAccountDO(c.env)),\n);\n\n// Server identity\napp.get(\"/xrpc/com.atproto.server.describeServer\", server.describeServer);\n\n// Handle resolution - return our DID for our handle, let others fall through to proxy\napp.use(\"/xrpc/com.atproto.identity.resolveHandle\", async (c, next) => {\n\tconst handle = c.req.query(\"handle\");\n\tif (handle === c.env.HANDLE) {\n\t\treturn c.json({ did: c.env.DID });\n\t}\n\tawait next();\n});\n\n// Session management\napp.post(\"/xrpc/com.atproto.server.createSession\", server.createSession);\napp.post(\"/xrpc/com.atproto.server.refreshSession\", server.refreshSession);\napp.get(\"/xrpc/com.atproto.server.getSession\", server.getSession);\napp.post(\"/xrpc/com.atproto.server.deleteSession\", server.deleteSession);\n\n// Account lifecycle\napp.get(\"/xrpc/com.atproto.server.getAccountStatus\", requireAuth, (c) =>\n\tserver.getAccountStatus(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.activateAccount\", requireAuth, (c) =>\n\tserver.activateAccount(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.deactivateAccount\", requireAuth, (c) =>\n\tserver.deactivateAccount(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/gg.mk.experimental.resetMigration\", requireAuth, (c) =>\n\tserver.resetMigration(c, getAccountDO(c.env)),\n);\n\n// Service auth - used by clients to get JWTs for external services (video, etc.)\napp.get(\n\t\"/xrpc/com.atproto.server.getServiceAuth\",\n\trequireAuth,\n\tserver.getServiceAuth,\n);\n\n// Actor preferences\napp.get(\"/xrpc/app.bsky.actor.getPreferences\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst result = await accountDO.rpcGetPreferences();\n\treturn c.json(result);\n});\napp.post(\"/xrpc/app.bsky.actor.putPreferences\", requireAuth, async (c) => {\n\tconst body = await c.req.json<{ preferences: unknown[] }>();\n\tconst accountDO = getAccountDO(c.env);\n\tawait accountDO.rpcPutPreferences(body.preferences);\n\treturn c.json({});\n});\n\n// Age assurance (stub - self-hosted users are pre-verified)\napp.get(\"/xrpc/app.bsky.ageassurance.getState\", requireAuth, (c) => {\n\treturn c.json({\n\t\tstate: {\n\t\t\tstatus: \"assured\",\n\t\t\taccess: \"full\",\n\t\t\tlastInitiatedAt: new Date().toISOString(),\n\t\t},\n\t\tmetadata: {\n\t\t\taccountCreatedAt: new Date().toISOString(),\n\t\t},\n\t});\n});\n\n// Admin: Emit identity event to refresh handle verification\napp.post(\"/admin/emit-identity\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst result = await accountDO.rpcEmitIdentityEvent(c.env.HANDLE);\n\treturn c.json(result);\n});\n\n// OAuth 2.1 endpoints for \"Login with Bluesky\"\nconst oauthApp = createOAuthApp(getAccountDO);\napp.route(\"/\", oauthApp);\n\n// Proxy unhandled XRPC requests to services specified via atproto-proxy header\n// or fall back to Bluesky services for backward compatibility\napp.all(\"/xrpc/*\", (c) => handleXrpcProxy(c, didResolver, getKeypair));\n\nexport default app;\n"],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AuBtTH,IAAa,kBAAb,MAA6B;CAC5B,AAAQ;CACR,AAAQ;CAER,YAAY,UAAqD,EAAE,EAAE;AACpE,OAAK,MAAM,QAAQ,YAAY,IAAI,UAAU;AAC7C,OAAK,aAAa,QAAQ,UAAU;AAGpC,OAAK,oBAAoB;;;;;;CAO1B,AAAQ,qBAA2B;EAElC,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;GAGf;AAGD,OAAK,MAAM,UAAU,OAAO,OAAO,QAAQ,CAC1C,MAAK,IAAI,IAAI,OAAO,QAAQ;;;;;;;;;CAW9B,eAAe,YAAoB,QAAuB;AAIzD,MAAI,CAFc,KAAK,UAAU,WAAW,EAE5B;AAEf,OAAI,KAAK,WACR,OAAM,IAAI,MACT,4CAA4C,WAAW,mDACvD;AAGF;;EAID,MAAM,YAAY,UAAU,OAAO;AAGnC,MAAI;AACH,QAAK,IAAI,kBAAkB,YAAY,UAAU;WACzC,OAAO;GACf,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,SAAM,IAAI,MACT,iCAAiC,WAAW,IAAI,UAChD;;;;;;CAOH,UAAU,YAA6B;AACtC,MAAI;AAEH,QAAK,IAAI,cAAc,WAAW;AAClC,UAAO;UACA;AACP,UAAO;;;;;;;;;;;;;;;;;CAkBT,UAAU,KAAgB;AACzB,OAAK,IAAI,IAAI,IAAI;;;;;CAMlB,mBAA6B;AAE5B,SAAO,MAAM,KAAK,KAAK,IAAI,CAAC,KAAK,QAAQ,IAAI,GAAG;;;;;CAMjD,cAAwB;AACvB,SAAO,KAAK;;;;;;;;;;;;;;;AAgBd,MAAa,YAAY,IAAI,gBAAgB,EAAE,QAAQ,OAAO,CAAC;;;;ACnI/D,SAAS,mBACR,GACA,KACA,QACW;CACX,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,SAAS,GAAG,OAAO,IAAI,YAAY;EAC5C,EACD,IACA;;;;;;;;AASF,SAAS,6BACR,GACA,KACkB;AAElB,MADgB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD,WAAW,sBAAsB,CAC5C,QAAO,EAAE,KACR;EACC,OAAO;EACP,SACC;EACD,EACD,IACA;AAEF,QAAO;;AAGR,eAAsB,aACrB,GACA,WACoB;CACpB,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO;AAEhC,KAAI,CAAC,KACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI;AACH,iBAAe,KAAK;UACZ,KAAK;AACb,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GAChF,EACD,IACA;;AAGF,KAAI,SAAS,EAAE,IAAI,IAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,yBAAyB;EAClC,EACD,IACA;CAGF,MAAM,OAAO,MAAM,UAAU,iBAAiB;AAE9C,QAAO,EAAE,KAAK;EACb,KAAK,EAAE,IAAI;EACX,QAAQ,EAAE,IAAI;EACd,QAAQ;GACP,YAAY,CAAC,+BAA+B;GAC5C,IAAI,EAAE,IAAI;GACV,aAAa,CAAC,QAAQ,EAAE,IAAI,SAAS;GACrC,oBAAoB,CACnB;IACC,IAAI,GAAG,EAAE,IAAI,IAAI;IACjB,MAAM;IACN,YAAY,EAAE,IAAI;IAClB,oBAAoB,EAAE,IAAI;IAC1B,CACD;GACD;EACD,aAAa,KAAK;EAClB,iBAAiB;EACjB,CAAC;;AAGH,eAAsB,UACrB,GACA,WACoB;CACpB,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO;CAChC,MAAM,aAAa,EAAE,IAAI,MAAM,aAAa;CAC5C,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO;AAEhC,KAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,KAC5B,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI;AACH,iBAAe,KAAK;UACZ,KAAK;AACb,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GAChF,EACD,IACA;;AAGF,KAAI,SAAS,EAAE,IAAI,IAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,yBAAyB;EAClC,EACD,IACA;CAGF,MAAM,SAAS,MAAM,UAAU,aAAa,YAAY,KAAK;AAE7D,KAAI,CAAC,OACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,qBAAqB,WAAW,GAAG;EAC5C,EACD,IACA;AAGF,QAAO,EAAE,KAAK;EACb,KAAK,MAAM,KAAK,MAAM,YAAY,KAAK,CAAC,UAAU;EAClD,KAAK,OAAO;EACZ,OAAO,OAAO;EACd,CAAC;;AAGH,eAAsB,YACrB,GACA,WACoB;CACpB,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO;CAChC,MAAM,aAAa,EAAE,IAAI,MAAM,aAAa;CAC5C,MAAM,WAAW,EAAE,IAAI,MAAM,QAAQ;CACrC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,aAAa,EAAE,IAAI,MAAM,UAAU;AAEzC,KAAI,CAAC,QAAQ,CAAC,WACb,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI;AACH,iBAAe,KAAK;UACZ,KAAK;AACb,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GAChF,EACD,IACA;;AAGF,KAAI,SAAS,EAAE,IAAI,IAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,yBAAyB;EAClC,EACD,IACA;CAGF,MAAM,QAAQ,KAAK,IAAI,WAAW,OAAO,SAAS,UAAU,GAAG,GAAG,IAAI,IAAI;CAC1E,MAAM,UAAU,eAAe;CAE/B,MAAM,SAAS,MAAM,UAAU,eAAe,YAAY;EACzD;EACA;EACA;EACA,CAAC;AAEF,QAAO,EAAE,KAAK,OAAO;;AAGtB,eAAsB,aACrB,GACA,WACoB;CAEpB,MAAM,EAAE,MAAM,YAAY,MAAM,WADnB,MAAM,EAAE,IAAI,MAAM;AAG/B,KAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,OAC5B,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,KAAI,SAAS,EAAE,IAAI,IAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,uBAAuB;EAChC,EACD,IACA;AAIF,KAAI;AACH,YAAU,eAAe,YAAY,OAAO;UACpC,KAAK;AACb,SAAO,mBAAmB,GAAG,IAAI;;AAGlC,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,gBAAgB,YAAY,MAAM,OAAO;AACxE,SAAO,EAAE,KAAK,OAAO;UACb,KAAK;EACb,MAAM,mBAAmB,6BAA6B,GAAG,IAAI;AAC7D,MAAI,iBAAkB,QAAO;AAE7B,QAAM;;;AAIR,eAAsB,aACrB,GACA,WACoB;CAEpB,MAAM,EAAE,MAAM,YAAY,SADb,MAAM,EAAE,IAAI,MAAM;AAG/B,KAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,KAC5B,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,KAAI,SAAS,EAAE,IAAI,IAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,uBAAuB;EAChC,EACD,IACA;AAGF,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,gBAAgB,YAAY,KAAK;AAEhE,MAAI,CAAC,OACJ,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,qBAAqB,WAAW,GAAG;GAC5C,EACD,IACA;AAGF,SAAO,EAAE,KAAK,OAAO;UACb,KAAK;EACb,MAAM,mBAAmB,6BAA6B,GAAG,IAAI;AAC7D,MAAI,iBAAkB,QAAO;AAE7B,QAAM;;;AAIR,eAAsB,UACrB,GACA,WACoB;CAEpB,MAAM,EAAE,MAAM,YAAY,MAAM,WADnB,MAAM,EAAE,IAAI,MAAM;AAG/B,KAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,OACrC,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,KAAI,SAAS,EAAE,IAAI,IAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,uBAAuB;EAChC,EACD,IACA;AAIF,KAAI;AACH,YAAU,eAAe,YAAY,OAAO;UACpC,KAAK;AACb,SAAO,mBAAmB,GAAG,IAAI;;AAGlC,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,aAAa,YAAY,MAAM,OAAO;AACrE,SAAO,EAAE,KAAK,OAAO;UACb,KAAK;EACb,MAAM,mBAAmB,6BAA6B,GAAG,IAAI;AAC7D,MAAI,iBAAkB,QAAO;AAE7B,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACzD,EACD,IACA;;;AAIH,eAAsB,YACrB,GACA,WACoB;CAEpB,MAAM,EAAE,MAAM,WADD,MAAM,EAAE,IAAI,MAAM;AAG/B,KAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,QAAQ,OAAO,CAC7C,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,KAAI,SAAS,EAAE,IAAI,IAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,uBAAuB;EAChC,EACD,IACA;AAGF,KAAI,OAAO,SAAS,IACnB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACvC,MAAM,QAAQ,OAAO;AACrB,MACC,MAAM,UAAU,yCAChB,MAAM,UAAU,sCAEhB,KAAI;AACH,aAAU,eAAe,MAAM,YAAY,MAAM,MAAM;WAC/C,KAAK;AACb,UAAO,mBAAmB,GAAG,KAAK,SAAS,IAAI;;;AAKlD,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,eAAe,OAAO;AACrD,SAAO,EAAE,KAAK,OAAO;UACb,KAAK;EACb,MAAM,mBAAmB,6BAA6B,GAAG,IAAI;AAC7D,MAAI,iBAAkB,QAAO;AAE7B,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACzD,EACD,IACA;;;AAIH,eAAsB,WACrB,GACA,WACoB;CACpB,MAAM,cACL,EAAE,IAAI,OAAO,eAAe,IAAI;CACjC,MAAM,QAAQ,IAAI,WAAW,MAAM,EAAE,IAAI,aAAa,CAAC;CAGvD,MAAM,gBAAgB,IAAI,OAAO;AACjC,KAAI,MAAM,SAAS,cAClB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,aAAa,MAAM,OAAO,sBAAsB,cAAc;EACvE,EACD,IACA;AAGF,KAAI;EACH,MAAM,UAAU,MAAM,UAAU,cAAc,OAAO,YAAY;AACjE,SAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;UACxB,KAAK;AACb,MACC,eAAe,SACf,IAAI,QAAQ,SAAS,8BAA8B,CAEnD,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS;GACT,EACD,IACA;AAEF,QAAM;;;AAIR,eAAsB,WACrB,GACA,WACoB;AAIpB,KAHoB,EAAE,IAAI,OAAO,eAAe,KAG5B,2BACnB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SACC;EACD,EACD,IACA;CAIF,MAAM,WAAW,IAAI,WAAW,MAAM,EAAE,IAAI,aAAa,CAAC;AAE1D,KAAI,SAAS,WAAW,EACvB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAIF,MAAM,eAAe,MAAM,OAAO;AAClC,KAAI,SAAS,SAAS,aACrB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,mBAAmB,SAAS,OAAO,sBAAsB,aAAa;EAC/E,EACD,IACA;AAGF,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,cAAc,SAAS;AACtD,SAAO,EAAE,KAAK,OAAO;UACb,KAAK;AACb,MAAI,eAAe,OAAO;AACzB,OAAI,IAAI,QAAQ,SAAS,iBAAiB,CACzC,QAAO,EAAE,KACR;IACC,OAAO;IACP,SACC;IACD,EACD,IACA;AAEF,OAAI,IAAI,QAAQ,SAAS,eAAe,CACvC,QAAO,EAAE,KACR;IACC,OAAO;IACP,SAAS,IAAI;IACb,EACD,IACA;AAEF,OACC,IAAI,QAAQ,SAAS,WAAW,IAChC,IAAI,QAAQ,SAAS,YAAY,IACjC,IAAI,QAAQ,SAAS,eAAe,CAEpC,QAAO,EAAE,KACR;IACC,OAAO;IACP,SAAS,qBAAqB,IAAI;IAClC,EACD,IACA;;AAGH,QAAM;;;;;;;AAQR,eAAsB,iBACrB,GACA,WACoB;CACpB,MAAM,WAAW,EAAE,IAAI,MAAM,QAAQ;CACrC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CAEpC,MAAM,QAAQ,WAAW,KAAK,IAAI,OAAO,SAAS,UAAU,GAAG,EAAE,IAAI,GAAG;CAExE,MAAM,SAAS,MAAM,UAAU,oBAC9B,OACA,UAAU,OACV;AAED,QAAO,EAAE,KAAK,OAAO;;;;;ACvjBtB,eAAsB,eAAe,GAAuC;AAC3E,QAAO,EAAE,KAAK;EACb,KAAK,EAAE,IAAI;EACX,sBAAsB,EAAE;EACxB,oBAAoB;EACpB,CAAC;;;;;AAMH,eAAsB,cAAc,GAAuC;CAM1E,MAAM,EAAE,YAAY,aALP,MAAM,EAAE,IAAI,MAGrB;AAIJ,KAAI,CAAC,cAAc,CAAC,SACnB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,eAAe,EAAE,IAAI,UAAU,eAAe,EAAE,IAAI,IACvD,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAKF,KAAI,CADkB,MAAMC,UAAe,UAAU,EAAE,IAAI,cAAc,CAExE,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAIF,MAAM,aAAa,WAAW,EAAE,IAAI;CACpC,MAAM,YAAY,MAAM,kBACvB,EAAE,IAAI,YACN,EAAE,IAAI,KACN,WACA;CACD,MAAM,aAAa,MAAM,mBACxB,EAAE,IAAI,YACN,EAAE,IAAI,KACN,WACA;AAED,QAAO,EAAE,KAAK;EACb;EACA;EACA,QAAQ,EAAE,IAAI;EACd,KAAK,EAAE,IAAI;EACX,QAAQ;EACR,CAAC;;;;;AAMH,eAAsB,eAAe,GAAuC;CAC3E,MAAM,aAAa,EAAE,IAAI,OAAO,gBAAgB;AAEhD,KAAI,CAAC,YAAY,WAAW,UAAU,CACrC,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAGF,MAAM,QAAQ,WAAW,MAAM,EAAE;CACjC,MAAM,aAAa,WAAW,EAAE,IAAI;AAEpC,KAAI;AAQH,OAPgB,MAAM,mBACrB,OACA,EAAE,IAAI,YACN,WACA,EAGW,QAAQ,EAAE,IAAI,IACzB,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS;GACT,EACD,IACA;EAIF,MAAM,YAAY,MAAM,kBACvB,EAAE,IAAI,YACN,EAAE,IAAI,KACN,WACA;EACD,MAAM,aAAa,MAAM,mBACxB,EAAE,IAAI,YACN,EAAE,IAAI,KACN,WACA;AAED,SAAO,EAAE,KAAK;GACb;GACA;GACA,QAAQ,EAAE,IAAI;GACd,KAAK,EAAE,IAAI;GACX,QAAQ;GACR,CAAC;UACM,KAAK;AACb,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,eAAe,QAAQ,IAAI,UAAU;GAC9C,EACD,IACA;;;;;;AAOH,eAAsB,WAAW,GAAuC;CACvE,MAAM,aAAa,EAAE,IAAI,OAAO,gBAAgB;AAEhD,KAAI,CAAC,YAAY,WAAW,UAAU,CACrC,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAGF,MAAM,QAAQ,WAAW,MAAM,EAAE;CACjC,MAAM,aAAa,WAAW,EAAE,IAAI;AAGpC,KAAI,UAAU,EAAE,IAAI,WACnB,QAAO,EAAE,KAAK;EACb,QAAQ,EAAE,IAAI;EACd,KAAK,EAAE,IAAI;EACX,QAAQ;EACR,CAAC;AAIH,KAAI;AAOH,OANgB,MAAM,kBACrB,OACA,EAAE,IAAI,YACN,WACA,EAEW,QAAQ,EAAE,IAAI,IACzB,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS;GACT,EACD,IACA;AAGF,SAAO,EAAE,KAAK;GACb,QAAQ,EAAE,IAAI;GACd,KAAK,EAAE,IAAI;GACX,QAAQ;GACR,CAAC;UACM,KAAK;AACb,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,eAAe,QAAQ,IAAI,UAAU;GAC9C,EACD,IACA;;;;;;AAOH,eAAsB,cAAc,GAAuC;AAI1E,QAAO,EAAE,KAAK,EAAE,CAAC;;;;;AAMlB,eAAsB,iBACrB,GACA,WACoB;AACpB,KAAI;EAEH,MAAM,SAAS,MAAM,UAAU,kBAAkB;EACjD,MAAM,SAAS,MAAM,UAAU,cAAc;EAG7C,MAAM,CAAC,YAAY,gBAAgB,eAAe,iBACjD,MAAM,QAAQ,IAAI;GACjB,UAAU,gBAAgB;GAC1B,UAAU,iBAAiB;GAC3B,UAAU,uBAAuB;GACjC,UAAU,uBAAuB;GACjC,CAAC;AAEH,SAAO,EAAE,KAAK;GACL;GACR,UAAU;GACV,YAAY,OAAO;GACnB,SAAS,OAAO;GAChB;GACA;GACA,oBAAoB;GACpB;GACA;GACA,CAAC;UACM,KAAK;AAEb,SAAO,EAAE,KAAK;GACb,QAAQ;GACR,UAAU;GACV,YAAY;GACZ,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,oBAAoB;GACpB,eAAe;GACf,eAAe;GACf,CAAC;;;;;;;AAQJ,eAAsB,eACrB,GACoB;CACpB,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;CAC9B,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM,IAAI;AAElC,KAAI,CAAC,IACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAIF,MAAM,UAAU,MAAM,kBAAkB,EAAE,IAAI,YAAY;CAC1D,MAAM,QAAQ,MAAM,iBAAiB;EACpC,KAAK,EAAE,IAAI;EACX;EACA;EACA;EACA,CAAC;AAEF,QAAO,EAAE,KAAK,EAAE,OAAO,CAAC;;;;;AAMzB,eAAsB,gBACrB,GACA,WACoB;AACpB,KAAI;AACH,QAAM,UAAU,oBAAoB;AACpC,SAAO,EAAE,KAAK,EAAE,SAAS,MAAM,CAAC;UACxB,KAAK;AACb,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,eAAe,QAAQ,IAAI,UAAU;GAC9C,EACD,IACA;;;;;;AAOH,eAAsB,kBACrB,GACA,WACoB;AACpB,KAAI;AACH,QAAM,UAAU,sBAAsB;AACtC,SAAO,EAAE,KAAK,EAAE,SAAS,MAAM,CAAC;UACxB,KAAK;AACb,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,eAAe,QAAQ,IAAI,UAAU;GAC9C,EACD,IACA;;;;;;;AAQH,eAAsB,eACrB,GACA,WACoB;AACpB,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,mBAAmB;AAClD,SAAO,EAAE,KAAK,OAAO;UACb,KAAK;EACb,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AAGrD,MAAI,QAAQ,SAAS,gBAAgB,CACpC,QAAO,EAAE,KACR;GACC,OAAO;GACP,SACC;GACD,EACD,IACA;AAGF,SAAO,EAAE,KACR;GACC,OAAO;GACP;GACA,EACD,IACA;;;;;;;;;;AErWH,MAAMC,QAAMC;AAcZ,KAAK,MAAM,OAXM;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAGA,KAAI,CAACD,MAAI,KACR,OAAM,IAAI,MAAM,0CAA0C,MAAM;AAKlE,IAAI;AACH,gBAAeA,MAAI,IAAI;AACvB,mBAAkBA,MAAI,OAAO;SACrB,KAAK;AACb,OAAM,IAAI,MACT,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC1E;;AAGF,MAAM,cAAc,IAAI,YAAY;CACnC,UAAU,IAAI,iBAAiB;CAC/B,SAAS;CACT,QAAQ;CACR,CAAC;AAGF,IAAIE,iBAAmD;AACvD,SAAS,aAAwC;AAChD,KAAI,CAAC,eACJ,kBAAiB,iBAAiB,OAAOF,MAAI,YAAY;AAE1D,QAAO;;AAGR,MAAM,MAAM,IAAI,MAA4B;AAG5C,IAAI,IACH,KACA,KAAK;CACJ,QAAQ;CACR,cAAc;EAAC;EAAO;EAAQ;EAAO;EAAU;EAAU;CACzD,cAAc,CAAC,IAAI;CACnB,eAAe,CAAC,eAAe;CAC/B,QAAQ;CACR,CAAC,CACF;AAGD,SAAS,aAAa,OAAa;CAClC,MAAMG,QAAKH,MAAI,QAAQ,WAAW,UAAU;AAC5C,QAAOA,MAAI,QAAQ,IAAIG,MAAG;;AAI3B,IAAI,IAAI,0BAA0B,MAAM;CACvC,MAAMC,gBAAc;EACnB,YAAY;GACX;GACA;GACA;GACA;EACD,IAAI,EAAE,IAAI;EACV,aAAa,CAAC,QAAQ,EAAE,IAAI,SAAS;EACrC,oBAAoB,CACnB;GACC,IAAI,GAAG,EAAE,IAAI,IAAI;GACjB,MAAM;GACN,YAAY,EAAE,IAAI;GAClB,oBAAoB,EAAE,IAAI;GAC1B,CACD;EACD,SAAS,CACR;GACC,IAAI;GACJ,MAAM;GACN,iBAAiB,WAAW,EAAE,IAAI;GAClC,CACD;EACD;AACD,QAAO,EAAE,KAAKA,cAAY;EACzB;AAIF,IAAI,IAAI,6BAA6B,MAAM;AAC1C,KAAI,EAAE,IAAI,WAAW,EAAE,IAAI,aAC1B,QAAO,EAAE,UAAU;AAEpB,QAAO,IAAI,SAAS,EAAE,IAAI,KAAK,EAC9B,SAAS,EAAE,gBAAgB,cAAc,EACzC,CAAC;EACD;AAGF,IAAI,IAAI,YAAY,MACnB,EAAE,KAAK;CACN,QAAQ;CACR;CACA,CAAC,CACF;AAGD,IAAI,IAAI,MAAM,MAAM;CACnB,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wDAgC0C,EAAE,IAAI,OAAO,qBAAqB,EAAE,IAAI,OAAO;wBAC/E,QAAQ;;;AAG/B,QAAO,EAAE,KAAK,KAAK;EAClB;AAGF,IAAI,IAAI,mCAAmC,MAC1CC,QAAa,GAAG,aAAa,EAAE,IAAI,CAAC,CACpC;AACD,IAAI,IAAI,yCAAyC,MAChDC,cAAmB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC1C;AACD,IAAI,IAAI,qCAAqC,MAC5CC,UAAe,GAAG,aAAa,EAAE,IAAI,CAAC,CACtC;AACD,IAAI,IAAI,mCAAmC,MAC1CC,QAAa,GAAG,aAAa,EAAE,IAAI,CAAC,CACpC;AACD,IAAI,IAAI,qCAAqC,MAC5CC,UAAe,GAAG,aAAa,EAAE,IAAI,CAAC,CACtC;AACD,IAAI,IAAI,qCAAqC,MAC5CC,UAAe,GAAG,aAAa,EAAE,IAAI,CAAC,CACtC;AAGD,IAAI,IAAI,yCAAyC,OAAO,MAAM;AAE7D,KADsB,EAAE,IAAI,OAAO,UAAU,KACvB,YACrB,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAA8B,EAClE,IACA;AAKF,QADkB,aAAa,EAAE,IAAI,CACpB,MAAM,EAAE,IAAI,IAAI;EAChC;AAGF,IAAI,IAAI,wCAAwC,MAC/CC,aAAkB,GAAG,aAAa,EAAE,IAAI,CAAC,CACzC;AACD,IAAI,IAAI,qCAAqC,MAC5CC,UAAe,GAAG,aAAa,EAAE,IAAI,CAAC,CACtC;AACD,IAAI,IAAI,uCAAuC,MAC9CC,YAAiB,GAAG,aAAa,EAAE,IAAI,CAAC,CACxC;AAGD,IAAI,KAAK,uCAAuC,cAAc,MAC7DC,aAAkB,GAAG,aAAa,EAAE,IAAI,CAAC,CACzC;AACD,IAAI,KAAK,uCAAuC,cAAc,MAC7DC,aAAkB,GAAG,aAAa,EAAE,IAAI,CAAC,CACzC;AACD,IAAI,KAAK,qCAAqC,cAAc,MAC3DC,WAAgB,GAAG,aAAa,EAAE,IAAI,CAAC,CACvC;AACD,IAAI,KAAK,sCAAsC,cAAc,MAC5DC,YAAiB,GAAG,aAAa,EAAE,IAAI,CAAC,CACxC;AACD,IAAI,KAAK,oCAAoC,cAAc,MAC1DC,UAAe,GAAG,aAAa,EAAE,IAAI,CAAC,CACtC;AACD,IAAI,KAAK,qCAAqC,cAAc,MAC3DC,WAAgB,GAAG,aAAa,EAAE,IAAI,CAAC,CACvC;AACD,IAAI,IAAI,2CAA2C,cAAc,MAChEC,iBAAsB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC7C;AAGD,IAAI,IAAI,2CAA2CC,eAAsB;AAGzE,IAAI,IAAI,4CAA4C,OAAO,GAAG,SAAS;AAEtE,KADe,EAAE,IAAI,MAAM,SAAS,KACrB,EAAE,IAAI,OACpB,QAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC;AAElC,OAAM,MAAM;EACX;AAGF,IAAI,KAAK,0CAA0CC,cAAqB;AACxE,IAAI,KAAK,2CAA2CC,eAAsB;AAC1E,IAAI,IAAI,uCAAuCC,WAAkB;AACjE,IAAI,KAAK,0CAA0CC,cAAqB;AAGxE,IAAI,IAAI,6CAA6C,cAAc,MAClEC,iBAAwB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC/C;AACD,IAAI,KAAK,4CAA4C,cAAc,MAClEC,gBAAuB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC9C;AACD,IAAI,KAAK,8CAA8C,cAAc,MACpEC,kBAAyB,GAAG,aAAa,EAAE,IAAI,CAAC,CAChD;AACD,IAAI,KAAK,2CAA2C,cAAc,MACjEC,eAAsB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC7C;AAGD,IAAI,IACH,2CACA,aACAC,eACA;AAGD,IAAI,IAAI,uCAAuC,aAAa,OAAO,MAAM;CAExE,MAAM,SAAS,MADG,aAAa,EAAE,IAAI,CACN,mBAAmB;AAClD,QAAO,EAAE,KAAK,OAAO;EACpB;AACF,IAAI,KAAK,uCAAuC,aAAa,OAAO,MAAM;CACzE,MAAM,OAAO,MAAM,EAAE,IAAI,MAAkC;AAE3D,OADkB,aAAa,EAAE,IAAI,CACrB,kBAAkB,KAAK,YAAY;AACnD,QAAO,EAAE,KAAK,EAAE,CAAC;EAChB;AAGF,IAAI,IAAI,wCAAwC,cAAc,MAAM;AACnE,QAAO,EAAE,KAAK;EACb,OAAO;GACN,QAAQ;GACR,QAAQ;GACR,kCAAiB,IAAI,MAAM,EAAC,aAAa;GACzC;EACD,UAAU,EACT,mCAAkB,IAAI,MAAM,EAAC,aAAa,EAC1C;EACD,CAAC;EACD;AAGF,IAAI,KAAK,wBAAwB,aAAa,OAAO,MAAM;CAE1D,MAAM,SAAS,MADG,aAAa,EAAE,IAAI,CACN,qBAAqB,EAAE,IAAI,OAAO;AACjE,QAAO,EAAE,KAAK,OAAO;EACpB;AAGF,MAAM,WAAW,eAAe,aAAa;AAC7C,IAAI,MAAM,KAAK,SAAS;AAIxB,IAAI,IAAI,YAAY,MAAM,gBAAgB,GAAG,aAAa,WAAW,CAAC;AAEtE,kBAAe"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getcirrus/pds",
3
- "version": "0.2.2",
3
+ "version": "0.2.5",
4
4
  "description": "Cirrus – A single-user AT Protocol PDS on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "hono": "^4.11.3",
30
30
  "jose": "^6.1.3",
31
31
  "picocolors": "^1.1.1",
32
- "@getcirrus/oauth-provider": "0.1.0"
32
+ "@getcirrus/oauth-provider": "0.1.2"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@arethetypeswrong/cli": "^0.18.2",