@getcirrus/pds 0.10.4 → 0.10.6

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["result: Record<string, unknown>","encode","atcuteEncode","decode","atcuteDecode","sql: SqlStorage","missing: CID[]","blobs: Array<{ cid: string; recordUri: string }>","sql: SqlStorage","now","sql: SqlStorage","eventPayload: Omit<CommitEvent, \"seq\">","cborEncode","events: SeqEvent[]","cborDecode","r2: R2Bucket","did: string","cidToString","createCid","env","tidNow","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 }>","carChunks: Uint8Array[]","cborEncode","result: Record<string, unknown>","cids: string[]","cachedKeypair: Secp256k1Keypair | null","cachedSigningKey: string | null","result: Partial<T>","payload: ServiceJwtPayload","now","payload: JWTPayload","protectedHeader: { typ?: string }","accountDO: DurableObjectStub<AccountDurableObject>","getAccountDO","env","getAccountDO: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>","token: string | undefined","PLC_DIRECTORY","stubbableFetch: typeof fetch","audienceDid: string","targetUrl: URL","didResolver","headers: Record<string, string>","userDid: string | undefined","getKeypair","reqInit: RequestInit","result: { cids: string[]; cursor?: string }","getRecord","recordSchemas: Record<string, BaseSchema>","verifyPassword","payload: TokenPayload","now","registrationScriptHashPromise: Promise<string> | null","env","_env","keypairPromise: Promise<Secp256k1Keypair> | null","sync.getRepo","sync.getRepoStatus","sync.getBlocks","sync.getBlob","sync.listRepos","sync.listBlobs","sync.getRecord","repo.describeRepo","repo.getRecord","repo.listRecords","repo.createRecord","repo.deleteRecord","repo.uploadBlob","repo.applyWrites","repo.putRecord","repo.importRepo","repo.listMissingBlobs","server.describeServer","identity.requestPlcOperationSignature","identity.signPlcOperation","identity.getMigrationToken","server.createSession","server.refreshSession","server.getSession","server.deleteSession","server.checkAccountStatus","server.activateAccount","server.deactivateAccount","server.resetMigration","server.requestEmailUpdate","server.requestEmailConfirmation","server.updateEmail","server.getServiceAuth","passkey.initPasskeyRegistration","passkey.getRegistrationOptions","passkey.completePasskeyRegistration","passkey.listPasskeys","passkey.deletePasskey"],"sources":["../src/cbor-compat.ts","../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/passkey.ts","../src/oauth.ts","../src/middleware/auth.ts","../src/did-resolver.ts","../src/did-cache.ts","../src/xrpc-proxy.ts","../src/format.ts","../src/xrpc/sync.ts","../src/validation.ts","../src/xrpc/repo.ts","../src/xrpc/server.ts","../src/migration-token.ts","../src/xrpc/identity.ts","../src/passkey-ui.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * CBOR compatibility layer for migrating from @atproto/lex-cbor to @atcute/cbor.\n *\n * @atcute/cbor uses lazy wrappers (BytesWrapper, CidLinkWrapper) that are\n * compatible with atproto's lex-json format. This layer handles conversion\n * of @atproto CID objects to CidLinkWrapper for encoding.\n *\n * Use toCidLink/fromCidLink to convert between lex-json and raw CID types.\n */\nimport {\n\tencode as atcuteEncode,\n\tdecode as atcuteDecode,\n\ttoCidLink,\n\ttoBytes,\n\tfromBytes,\n\tisBytes,\n\ttype CidLink,\n} from \"@atcute/cbor\";\nimport { fromString } from \"@atcute/cid\";\nimport type { CID } from \"@atproto/lex-data\";\n\n/**\n * Check if a value is an @atproto CID object.\n */\nfunction isAtprotoCid(value: unknown): value is CID {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\tconst obj = value as Record<string | symbol, unknown>;\n\treturn \"asCID\" in obj && obj[Symbol.toStringTag] === \"CID\";\n}\n\n/**\n * Convert @atproto CID to @atcute CidLink.\n */\nfunction atprotoCidToCidLink(cid: CID): CidLink {\n\treturn toCidLink(fromString(cid.toString()));\n}\n\n/**\n * Recursively convert @atproto CIDs to @atcute CidLinks for encoding.\n */\nfunction convertCidsForEncode(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn value;\n\t}\n\n\tif (typeof value !== \"object\") {\n\t\treturn value;\n\t}\n\n\t// Handle Uint8Array - wrap with toBytes() for @atcute/cbor\n\tif (ArrayBuffer.isView(value) && value instanceof Uint8Array) {\n\t\treturn toBytes(value);\n\t}\n\n\t// Convert @atproto CID to @atcute CidLink\n\tif (isAtprotoCid(value)) {\n\t\treturn atprotoCidToCidLink(value);\n\t}\n\n\t// Handle arrays\n\tif (Array.isArray(value)) {\n\t\treturn value.map(convertCidsForEncode);\n\t}\n\n\t// Handle plain objects\n\tconst obj = value as object;\n\tif (obj.constructor === Object) {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, val] of Object.entries(obj)) {\n\t\t\tresult[key] = convertCidsForEncode(val);\n\t\t}\n\t\treturn result;\n\t}\n\n\treturn value;\n}\n\n/**\n * Encode a value to CBOR, automatically converting @atproto CIDs to CidLinks.\n *\n * Decoded values will contain CidLinkWrapper objects which have a lazy $link\n * getter returning the CID string - compatible with lex-json format.\n */\nexport function encode(value: unknown): Uint8Array {\n\tconst converted = convertCidsForEncode(value);\n\treturn atcuteEncode(converted);\n}\n\n/**\n * Recursively convert @atcute wrappers back to raw types for decoding.\n */\nfunction convertWrappersForDecode(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn value;\n\t}\n\n\tif (typeof value !== \"object\") {\n\t\treturn value;\n\t}\n\n\t// Unwrap BytesWrapper to raw Uint8Array\n\tif (isBytes(value)) {\n\t\treturn fromBytes(value);\n\t}\n\n\t// CidLinkWrapper is left as-is since it has $link getter for lex-json compat\n\n\t// Handle arrays\n\tif (Array.isArray(value)) {\n\t\treturn value.map(convertWrappersForDecode);\n\t}\n\n\t// Handle plain objects\n\tconst obj = value as object;\n\tif (obj.constructor === Object) {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, val] of Object.entries(obj)) {\n\t\t\tresult[key] = convertWrappersForDecode(val);\n\t\t}\n\t\treturn result;\n\t}\n\n\treturn value;\n}\n\n/**\n * Decode CBOR bytes.\n *\n * Unwraps BytesWrapper to raw Uint8Array for compatibility.\n * CidLinkWrapper is left as-is (access via .$link for lex-json compat).\n */\nexport function decode(bytes: Uint8Array): unknown {\n\tconst decoded = atcuteDecode(bytes);\n\treturn convertWrappersForDecode(decoded);\n}\n","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\temail TEXT\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\n\t\t\t-- Collection name cache (for describeRepo)\n\t\t\tCREATE TABLE IF NOT EXISTS collections (\n\t\t\t\tcollection TEXT PRIMARY KEY\n\t\t\t);\n\n\t\t\t-- Passkey credentials (WebAuthn)\n\t\t\tCREATE TABLE IF NOT EXISTS passkeys (\n\t\t\t\tcredential_id TEXT PRIMARY KEY,\n\t\t\t\tpublic_key BLOB NOT NULL,\n\t\t\t\tcounter INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tname TEXT,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tlast_used_at TEXT\n\t\t\t);\n\n\t\t\t-- Passkey registration tokens (short-lived, 10 min TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS passkey_tokens (\n\t\t\t\ttoken TEXT PRIMARY KEY,\n\t\t\t\tchallenge TEXT NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL,\n\t\t\t\tname TEXT\n\t\t\t);\n\t\t`);\n\n\t\t// Migration: add email column for existing databases\n\t\ttry {\n\t\t\tthis.sql.exec(\"ALTER TABLE repo_state ADD COLUMN email TEXT\");\n\t\t} catch {\n\t\t\t// Column already exists\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 * Get the stored email address.\n\t */\n\tgetEmail(): string | null {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT email FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.email as string) ?? null) : null;\n\t}\n\n\t/**\n\t * Set the email address.\n\t */\n\tsetEmail(email: string): void {\n\t\tthis.sql.exec(\"UPDATE repo_state SET email = ? WHERE id = 1\", email);\n\t}\n\n\t// ============================================\n\t// Collection Cache Methods\n\t// ============================================\n\n\t/**\n\t * Get all cached collection names.\n\t */\n\tgetCollections(): string[] {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT collection FROM collections ORDER BY collection\")\n\t\t\t.toArray();\n\t\treturn rows.map((row) => row.collection as string);\n\t}\n\n\t/**\n\t * Add a collection name to the cache (no-op if already present).\n\t */\n\taddCollection(collection: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR IGNORE INTO collections (collection) VALUES (?)\",\n\t\t\tcollection,\n\t\t);\n\t}\n\n\t/**\n\t * Check if the collections cache has been populated.\n\t */\n\thasCollections(): boolean {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT 1 FROM collections LIMIT 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0;\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\t// ============================================\n\t// Passkey Methods\n\t// ============================================\n\n\t/**\n\t * Save a passkey credential.\n\t */\n\tsavePasskey(\n\t\tcredentialId: string,\n\t\tpublicKey: Uint8Array,\n\t\tcounter: number,\n\t\tname?: string,\n\t): void {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO passkeys (credential_id, public_key, counter, name)\n\t\t\t VALUES (?, ?, ?, ?)`,\n\t\t\tcredentialId,\n\t\t\tpublicKey,\n\t\t\tcounter,\n\t\t\tname ?? null,\n\t\t);\n\t}\n\n\t/**\n\t * Get a passkey by credential ID.\n\t */\n\tgetPasskey(credentialId: string): {\n\t\tcredentialId: string;\n\t\tpublicKey: Uint8Array;\n\t\tcounter: number;\n\t\tname: string | null;\n\t\tcreatedAt: string;\n\t\tlastUsedAt: string | null;\n\t} | null {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT credential_id, public_key, counter, name, created_at, last_used_at\n\t\t\t\t FROM passkeys WHERE credential_id = ?`,\n\t\t\t\tcredentialId,\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\tcredentialId: row.credential_id as string,\n\t\t\tpublicKey: new Uint8Array(row.public_key as ArrayBuffer),\n\t\t\tcounter: row.counter as number,\n\t\t\tname: row.name as string | null,\n\t\t\tcreatedAt: row.created_at as string,\n\t\t\tlastUsedAt: row.last_used_at as string | null,\n\t\t};\n\t}\n\n\t/**\n\t * List all passkeys.\n\t */\n\tlistPasskeys(): Array<{\n\t\tcredentialId: string;\n\t\tname: string | null;\n\t\tcreatedAt: string;\n\t\tlastUsedAt: string | null;\n\t}> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT credential_id, name, created_at, last_used_at\n\t\t\t\t FROM passkeys ORDER BY created_at DESC`,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\treturn rows.map((row) => ({\n\t\t\tcredentialId: row.credential_id as string,\n\t\t\tname: row.name as string | null,\n\t\t\tcreatedAt: row.created_at as string,\n\t\t\tlastUsedAt: row.last_used_at as string | null,\n\t\t}));\n\t}\n\n\t/**\n\t * Delete a passkey.\n\t */\n\tdeletePasskey(credentialId: string): boolean {\n\t\tconst before = this.sql.exec(\"SELECT COUNT(*) as c FROM passkeys\").one();\n\t\tthis.sql.exec(\"DELETE FROM passkeys WHERE credential_id = ?\", credentialId);\n\t\tconst after = this.sql.exec(\"SELECT COUNT(*) as c FROM passkeys\").one();\n\t\treturn (before.c as number) > (after.c as number);\n\t}\n\n\t/**\n\t * Update passkey counter after successful authentication.\n\t */\n\tupdatePasskeyCounter(credentialId: string, counter: number): void {\n\t\tthis.sql.exec(\n\t\t\t`UPDATE passkeys SET counter = ?, last_used_at = datetime('now')\n\t\t\t WHERE credential_id = ?`,\n\t\t\tcounter,\n\t\t\tcredentialId,\n\t\t);\n\t}\n\n\t/**\n\t * Check if any passkeys exist (for conditional UI).\n\t */\n\thasPasskeys(): boolean {\n\t\tconst result = this.sql.exec(\"SELECT COUNT(*) as c FROM passkeys\").one();\n\t\treturn (result.c as number) > 0;\n\t}\n\n\t// ============================================\n\t// Passkey Registration Token Methods\n\t// ============================================\n\n\t/**\n\t * Save a registration token with challenge and optional name.\n\t */\n\tsavePasskeyToken(token: string, challenge: string, expiresAt: number, name?: string): void {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO passkey_tokens (token, challenge, expires_at, name) VALUES (?, ?, ?, ?)`,\n\t\t\ttoken,\n\t\t\tchallenge,\n\t\t\texpiresAt,\n\t\t\tname ?? null,\n\t\t);\n\t}\n\n\t/**\n\t * Get and consume a registration token.\n\t */\n\tconsumePasskeyToken(token: string): { challenge: string; name: string | null } | null {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT challenge, expires_at, name FROM passkey_tokens WHERE token = ?`,\n\t\t\t\ttoken,\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\t// Delete the token (single-use)\n\t\tthis.sql.exec(\"DELETE FROM passkey_tokens WHERE token = ?\", token);\n\n\t\t// Check if expired\n\t\tif (Date.now() > expiresAt) return null;\n\n\t\treturn { challenge: row.challenge as string, name: (row.name as string) ?? null };\n\t}\n\n\t/**\n\t * Clean up expired tokens.\n\t */\n\tcleanupPasskeyTokens(): void {\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM passkey_tokens WHERE expires_at < ?\",\n\t\t\tDate.now(),\n\t\t);\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\n\t\t\t-- WebAuthn challenges for passkey authentication (2 min TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_webauthn_challenges (\n\t\t\t\tchallenge 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_challenges_created ON oauth_webauthn_challenges(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\t// WebAuthn challenges expire after 2 minutes\n\t\tconst challengeExpiry = now - 2 * 60 * 1000;\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_webauthn_challenges WHERE created_at < ?\",\n\t\t\tchallengeExpiry,\n\t\t);\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\tthis.sql.exec(\"DELETE FROM oauth_webauthn_challenges\");\n\t}\n\n\t// ============================================\n\t// WebAuthn Challenges\n\t// ============================================\n\n\t/**\n\t * Save a WebAuthn challenge for later verification\n\t */\n\tsaveWebAuthnChallenge(challenge: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT INTO oauth_webauthn_challenges (challenge, created_at) VALUES (?, ?)\",\n\t\t\tchallenge,\n\t\t\tDate.now(),\n\t\t);\n\t}\n\n\t/**\n\t * Consume a WebAuthn challenge (single-use, deleted after retrieval)\n\t * @returns true if challenge was valid and consumed, false if not found or expired\n\t */\n\tconsumeWebAuthnChallenge(challenge: string): boolean {\n\t\t// Check if challenge exists and is not expired (2 min TTL)\n\t\tconst twoMinutesAgo = Date.now() - 2 * 60 * 1000;\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t\"SELECT challenge FROM oauth_webauthn_challenges WHERE challenge = ? AND created_at > ?\",\n\t\t\t\tchallenge,\n\t\t\t\ttwoMinutesAgo,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Delete the challenge (single-use)\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_webauthn_challenges WHERE challenge = ?\",\n\t\t\tchallenge,\n\t\t);\n\n\t\treturn true;\n\t}\n}\n","import { encode as cborEncode, decode as cborDecode } from \"./cbor-compat\";\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 * Identity event payload for the firehose\n */\nexport interface IdentityEvent {\n\tseq: number;\n\tdid: string;\n\thandle: string;\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 commit event wrapper\n */\nexport interface SeqCommitEvent {\n\tseq: number;\n\ttype: \"commit\";\n\tevent: CommitEvent;\n\ttime: string;\n}\n\n/**\n * Sequenced identity event wrapper\n */\nexport interface SeqIdentityEvent {\n\tseq: number;\n\ttype: \"identity\";\n\tevent: IdentityEvent;\n\ttime: string;\n}\n\n/**\n * Sequenced event (commit or identity)\n */\nexport type SeqEvent = SeqCommitEvent | SeqIdentityEvent;\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);\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 * Skips identity events that have empty payloads.\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\tconst events: SeqEvent[] = [];\n\n\t\tfor (const row of rows) {\n\t\t\tconst eventType = row.event_type as string;\n\t\t\tconst payload = new Uint8Array(row.payload as ArrayBuffer);\n\t\t\tconst seq = row.seq as number;\n\t\t\tconst time = row.created_at as string;\n\n\t\t\tif (eventType === \"identity\") {\n\t\t\t\t// Skip legacy identity events with empty payload\n\t\t\t\tif (payload.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Decode identity event with proper payload\n\t\t\t\tconst decoded = cborDecode(payload) as Omit<IdentityEvent, \"seq\">;\n\t\t\t\tevents.push({\n\t\t\t\t\tseq,\n\t\t\t\t\ttype: \"identity\",\n\t\t\t\t\tevent: { ...decoded, seq },\n\t\t\t\t\ttime,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t// Commit event\n\t\t\t\tconst decoded = cborDecode(payload) as Omit<CommitEvent, \"seq\">;\n\t\t\t\tevents.push({\n\t\t\t\t\tseq,\n\t\t\t\t\ttype: \"commit\",\n\t\t\t\t\tevent: { ...decoded, seq },\n\t\t\t\t\ttime,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn events;\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 { create as createCid, CODEC_RAW, toString as cidToString } from \"@atcute/cid\";\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 cidObj = await createCid(CODEC_RAW, bytes);\n\t\tconst cidStr = cidToString(cidObj);\n\n\t\t// Store in R2 with DID prefix for isolation\n\t\tconst key = `${this.did}/${cidStr}`;\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: cidStr },\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 string.\n\t */\n\tasync getBlob(cid: string): Promise<R2ObjectBody | null> {\n\t\tconst key = `${this.did}/${cid}`;\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: string): Promise<boolean> {\n\t\tconst key = `${this.did}/${cid}`;\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\tgetRecords,\n\ttype RecordCreateOp,\n\ttype RecordUpdateOp,\n\ttype RecordDeleteOp,\n\ttype RecordWriteOp,\n} from \"@atproto/repo\";\n/** Record type compatible with @atproto/repo operations */\ntype RepoRecord = Record<string, unknown>;\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { CID, asCid, isBlobRef } from \"@atproto/lex-data\";\nimport { now as tidNow } from \"@atcute/tid\";\nimport { encode as cborEncode } from \"./cbor-compat\";\nimport { SqliteRepoStorage } from \"./storage\";\nimport { SqliteOAuthStorage } from \"./oauth-storage\";\nimport {\n\tSequencer,\n\ttype SeqEvent,\n\ttype SeqCommitEvent,\n\ttype SeqIdentityEvent,\n\ttype CommitData,\n} from \"./sequencer\";\nimport { BlobStore, type BlobRef } from \"./blobs\";\nimport { jsonToLex } from \"@atproto/lex-json\";\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\n\t\t\t\t// Run cleanup on initialization\n\t\t\t\tthis.runCleanup();\n\n\t\t\t\t// Schedule periodic cleanup (run every 24 hours)\n\t\t\t\tconst currentAlarm = await this.ctx.storage.getAlarm();\n\t\t\t\tif (currentAlarm === null) {\n\t\t\t\t\tawait this.ctx.storage.setAlarm(Date.now() + 86400000); // 24 hours\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Run cleanup on storage to remove expired entries\n\t */\n\tprivate runCleanup(): void {\n\t\tif (this.storage) {\n\t\t\tthis.storage.cleanupPasskeyTokens();\n\t\t}\n\t\tif (this.oauthStorage) {\n\t\t\tthis.oauthStorage.cleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Alarm handler for periodic cleanup\n\t * Called by Cloudflare Workers when the alarm fires\n\t */\n\toverride async alarm(): Promise<void> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\t// Run cleanup\n\t\tthis.runCleanup();\n\n\t\t// Schedule next cleanup in 24 hours\n\t\tawait this.ctx.storage.setAlarm(Date.now() + 86400000);\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 storage = await this.getStorage();\n\n\t\t// Lazy backfill: if the cache is empty and the repo has content, populate it\n\t\tif (!storage.hasCollections() && (await storage.getRoot())) {\n\t\t\tconst seen = new Set<string>();\n\t\t\tfor await (const record of repo.walkRecords()) {\n\t\t\t\tif (!seen.has(record.collection)) {\n\t\t\t\t\tseen.add(record.collection);\n\t\t\t\t\tstorage.addCollection(record.collection);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tdid: repo.did,\n\t\t\tcollections: storage.getCollections(),\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: `at://${repo.did}/${record.collection}/${record.rkey}`,\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 || tidNow();\n\t\tconst createOp: RecordCreateOp = {\n\t\t\taction: WriteOpAction.Create,\n\t\t\tcollection,\n\t\t\trkey: actualRkey,\n\t\t\trecord: jsonToLex(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// Update collections cache\n\t\tthis.storage!.addCollection(collection);\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: `at://${this.repo.did}/${collection}/${actualRkey}`,\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 normalizedRecord = jsonToLex(record) as RepoRecord;\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: normalizedRecord,\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: normalizedRecord,\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// Update collections cache\n\t\tthis.storage!.addCollection(collection);\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: `at://${this.repo.did}/${collection}/${rkey}`,\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 || tidNow();\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: jsonToLex(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: jsonToLex(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// Update collections cache for create/update ops\n\t\tfor (const op of ops) {\n\t\t\tif (op.action !== WriteOpAction.Delete) {\n\t\t\t\tthis.storage!.addCollection(op.collection);\n\t\t\t}\n\t\t}\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: `at://${this.repo.did}/${result.collection}/${result.rkey}`,\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: Get record with proof as CAR file.\n\t * Returns the commit block and all MST blocks needed to verify\n\t * the existence (or non-existence) of a record.\n\t * Used by com.atproto.sync.getRecord for record verification.\n\t */\n\tasync rpcGetRecordProof(\n\t\tcollection: string,\n\t\trkey: string,\n\t): 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// Use @atproto/repo's getRecords to generate the proof CAR\n\t\t// This returns an async iterable of CAR chunks\n\t\tconst carChunks: Uint8Array[] = [];\n\t\tfor await (const chunk of getRecords(storage, root, [\n\t\t\t{ collection, rkey },\n\t\t])) {\n\t\t\tcarChunks.push(chunk);\n\t\t}\n\n\t\t// Concatenate all chunks into a single Uint8Array\n\t\tconst totalLength = carChunks.reduce((acc, chunk) => acc + chunk.length, 0);\n\t\tconst result = new Uint8Array(totalLength);\n\t\tlet offset = 0;\n\t\tfor (const chunk of carChunks) {\n\t\t\tresult.set(chunk, offset);\n\t\t\toffset += chunk.length;\n\t\t}\n\n\t\treturn result;\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 = tidNow();\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 and collection names from all imported records\n\t\tconst seenCollections = new Set<string>();\n\t\tfor await (const record of this.repo.walkRecords()) {\n\t\t\tif (!seenCollections.has(record.collection)) {\n\t\t\t\tseenCollections.add(record.collection);\n\t\t\t\tthis.storage!.addCollection(record.collection);\n\t\t\t}\n\t\t\tconst blobCids = extractBlobCids(record.record);\n\t\t\tif (blobCids.length > 0) {\n\t\t\t\tconst uri = `at://${this.repo.did}/${record.collection}/${record.rkey}`;\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 (60MB)\n\t\tconst MAX_BLOB_SIZE = 60 * 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\t\treturn this.blobStore.getBlob(cidStr);\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: SeqCommitEvent): 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 identity event frame.\n\t */\n\tprivate encodeIdentityFrame(event: SeqIdentityEvent): Uint8Array {\n\t\tconst header = { op: 1, t: \"#identity\" };\n\t\treturn this.encodeFrame(header, event.event);\n\t}\n\n\t/**\n\t * Encode any event frame based on its type.\n\t */\n\tprivate encodeEventFrame(event: SeqEvent): Uint8Array {\n\t\tif (event.type === \"identity\") {\n\t\t\treturn this.encodeIdentityFrame(event);\n\t\t}\n\t\treturn this.encodeCommitFrame(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.encodeEventFrame(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.encodeEventFrame(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 stored email\n\t */\n\tasync rpcGetEmail(): Promise<{ email: string | null }> {\n\t\tconst storage = await this.getStorage();\n\t\treturn { email: storage.getEmail() };\n\t}\n\n\t/**\n\t * RPC method: Update stored email\n\t */\n\tasync rpcUpdateEmail(email: string): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.setEmail(email);\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<{\n\t\tblobs: Array<{ cid: string; recordUri: string }>;\n\t\tcursor?: string;\n\t}> {\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(header);\n\t\tconst bodyBytes = cborEncode(body);\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// Health Check RPC Methods\n\t// ============================================\n\n\t/**\n\t * RPC method: Health check - verifies storage is accessible\n\t */\n\tasync rpcHealthCheck(): Promise<{ ok: true }> {\n\t\tthis.ctx.storage.sql.exec(\"SELECT 1\").toArray();\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * RPC method: Firehose status - returns subscriber count and latest sequence\n\t */\n\tasync rpcGetFirehoseStatus(): Promise<{\n\t\tsubscribers: number;\n\t\tlatestSeq: number | null;\n\t}> {\n\t\tconst sockets = this.ctx.getWebSockets();\n\t\tawait this.ensureStorageInitialized();\n\t\tconst storage = await this.getStorage();\n\t\tconst seq = await storage.getSeq();\n\t\treturn {\n\t\t\tsubscribers: sockets.length,\n\t\t\tlatestSeq: seq || null,\n\t\t};\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// Passkey RPC Methods\n\t// ============================================\n\n\t/** Save a passkey credential */\n\tasync rpcSavePasskey(\n\t\tcredentialId: string,\n\t\tpublicKey: Uint8Array,\n\t\tcounter: number,\n\t\tname?: string,\n\t): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.savePasskey(credentialId, publicKey, counter, name);\n\t}\n\n\t/** Get a passkey by credential ID */\n\tasync rpcGetPasskey(credentialId: string): Promise<{\n\t\tcredentialId: string;\n\t\tpublicKey: Uint8Array;\n\t\tcounter: number;\n\t\tname: string | null;\n\t\tcreatedAt: string;\n\t\tlastUsedAt: string | null;\n\t} | null> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.getPasskey(credentialId);\n\t}\n\n\t/** List all passkeys */\n\tasync rpcListPasskeys(): Promise<Array<{\n\t\tcredentialId: string;\n\t\tname: string | null;\n\t\tcreatedAt: string;\n\t\tlastUsedAt: string | null;\n\t}>> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.listPasskeys();\n\t}\n\n\t/** Delete a passkey */\n\tasync rpcDeletePasskey(credentialId: string): Promise<boolean> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.deletePasskey(credentialId);\n\t}\n\n\t/** Update passkey counter after authentication */\n\tasync rpcUpdatePasskeyCounter(\n\t\tcredentialId: string,\n\t\tcounter: number,\n\t): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.updatePasskeyCounter(credentialId, counter);\n\t}\n\n\t/** Check if passkeys exist */\n\tasync rpcHasPasskeys(): Promise<boolean> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.hasPasskeys();\n\t}\n\n\t/** Save a registration token */\n\tasync rpcSavePasskeyToken(\n\t\ttoken: string,\n\t\tchallenge: string,\n\t\texpiresAt: number,\n\t\tname?: string,\n\t): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.savePasskeyToken(token, challenge, expiresAt, name);\n\t}\n\n\t/** Consume a registration token */\n\tasync rpcConsumePasskeyToken(\n\t\ttoken: string,\n\t): Promise<{ challenge: string; name: string | null } | null> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.consumePasskeyToken(token);\n\t}\n\n\t/** Save a WebAuthn challenge for passkey authentication */\n\tasync rpcSaveWebAuthnChallenge(challenge: string): Promise<void> {\n\t\tconst oauthStorage = await this.getOAuthStorage();\n\t\toauthStorage.saveWebAuthnChallenge(challenge);\n\t}\n\n\t/** Consume a WebAuthn challenge (single-use) */\n\tasync rpcConsumeWebAuthnChallenge(challenge: string): Promise<boolean> {\n\t\tconst oauthStorage = await this.getOAuthStorage();\n\t\treturn oauthStorage.consumeWebAuthnChallenge(challenge);\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\t// Convert Uint8Array to { $bytes: \"<base64>\" }\n\tif (obj instanceof Uint8Array) {\n\t\tlet binary = \"\";\n\t\tfor (let i = 0; i < obj.length; i++) {\n\t\t\tbinary += String.fromCharCode(obj[i]!);\n\t\t}\n\t\treturn { $bytes: btoa(binary) };\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;\n\n// Service JWTs for external services (like video.bsky.app) need longer expiry\n// because video processing can take several minutes before the callback arrives.\nconst SERVICE_JWT_EXPIRY_SECONDS = 5 * MINUTE;\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 + SERVICE_JWT_EXPIRY_SECONDS;\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, errors, type JWTPayload } from \"jose\";\nimport { compare } from \"bcryptjs\";\n\n/**\n * Error thrown when a JWT has expired.\n * Callers should return HTTP 400 with error code 'ExpiredToken'.\n */\nexport class TokenExpiredError extends Error {\n\tconstructor(message = \"Token has expired\") {\n\t\tsuper(message);\n\t\tthis.name = \"TokenExpiredError\";\n\t}\n}\n\n// Match official PDS: 120 minutes for session access tokens\nconst ACCESS_TOKEN_LIFETIME = \"120m\";\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: \"com.atproto.access\" })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"at+jwt\" })\n\t\t.setIssuedAt()\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\" })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"refresh+jwt\" })\n\t\t.setIssuedAt()\n\t\t.setAudience(serviceDid)\n\t\t.setSubject(userDid)\n\t\t.setJti(jti)\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 * Throws TokenExpiredError if the token has expired.\n */\nexport async function verifyAccessToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tlet payload: JWTPayload;\n\tlet protectedHeader: { typ?: string };\n\n\ttry {\n\t\tconst result = await jwtVerify(token, secret, {\n\t\t\taudience: serviceDid,\n\t\t});\n\t\tpayload = result.payload;\n\t\tprotectedHeader = result.protectedHeader;\n\t} catch (err) {\n\t\tif (err instanceof errors.JWTExpired) {\n\t\t\tthrow new TokenExpiredError();\n\t\t}\n\t\tthrow err;\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 !== \"com.atproto.access\") {\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 * Throws TokenExpiredError if the token has expired.\n */\nexport async function verifyRefreshToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tlet payload: JWTPayload;\n\tlet protectedHeader: { typ?: string };\n\n\ttry {\n\t\tconst result = await jwtVerify(token, secret, {\n\t\t\taudience: serviceDid,\n\t\t});\n\t\tpayload = result.payload;\n\t\tprotectedHeader = result.protectedHeader;\n\t} catch (err) {\n\t\tif (err instanceof errors.JWTExpired) {\n\t\t\tthrow new TokenExpiredError();\n\t\t}\n\t\tthrow err;\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 * Passkey (WebAuthn) support for Cirrus PDS\n *\n * Handles passkey registration and authentication using the @simplewebauthn/server library.\n * Uses a CLI-driven flow where:\n * 1. CLI generates a registration token\n * 2. User opens the URL on their device\n * 3. Device performs the WebAuthn ceremony\n * 4. Passkey is stored in the Durable Object\n */\n\nimport {\n\tgenerateRegistrationOptions,\n\tverifyRegistrationResponse,\n\tgenerateAuthenticationOptions,\n\tverifyAuthenticationResponse,\n} from \"@simplewebauthn/server\";\nimport type {\n\tAuthenticationResponseJSON,\n\tRegistrationResponseJSON,\n} from \"@simplewebauthn/server\";\nimport type { AccountDurableObject } from \"./account-do\";\n\n// Re-export for use by other modules (e.g., oauth.ts)\nexport type { AuthenticationResponseJSON, RegistrationResponseJSON };\n\n/** Options for creating a new credential */\nexport interface PublicKeyCredentialCreationOptionsJSON {\n\trp: { name: string; id?: string };\n\tuser: { id: string; name: string; displayName: string };\n\tchallenge: string;\n\tpubKeyCredParams: Array<{ alg: number; type: string }>;\n\ttimeout?: number;\n\tattestation?: string;\n\tauthenticatorSelection?: {\n\t\tauthenticatorAttachment?: string;\n\t\trequireResidentKey?: boolean;\n\t\tresidentKey?: string;\n\t\tuserVerification?: string;\n\t};\n\texcludeCredentials?: Array<{\n\t\tid: string;\n\t\ttype?: string;\n\t\ttransports?: string[];\n\t}>;\n}\n\n/** Options for authenticating with an existing credential */\ninterface PublicKeyCredentialRequestOptionsJSON {\n\tchallenge: string;\n\ttimeout?: number;\n\trpId?: string;\n\tallowCredentials?: Array<{\n\t\tid: string;\n\t\ttype?: string;\n\t\ttransports?: string[];\n\t}>;\n\tuserVerification?: string;\n}\n\n/** Token TTL in milliseconds (10 minutes) */\nconst TOKEN_TTL_MS = 10 * 60 * 1000;\n\n/**\n * Generate a secure random token\n */\nfunction generateToken(): string {\n\tconst bytes = new Uint8Array(32);\n\tcrypto.getRandomValues(bytes);\n\t// Use base64url encoding (URL-safe)\n\treturn btoa(String.fromCharCode(...bytes))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=/g, \"\");\n}\n\nexport interface PasskeyRegistrationInit {\n\t/** Token to include in the registration URL */\n\ttoken: string;\n\t/** Full URL for the user to visit */\n\turl: string;\n\t/** When this token expires */\n\texpiresAt: number;\n}\n\nexport interface PasskeyInfo {\n\t/** Credential ID (base64url encoded) */\n\tid: string;\n\t/** User-provided name for this passkey */\n\tname: string | null;\n\t/** When the passkey was created */\n\tcreatedAt: string;\n\t/** When the passkey was last used */\n\tlastUsedAt: string | null;\n}\n\n/**\n * Initialize passkey registration\n *\n * Generates a registration token and challenge, stores them,\n * and returns the URL for the user to visit.\n */\nexport async function initPasskeyRegistration(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\tdid: string,\n\tname?: string,\n): Promise<PasskeyRegistrationInit> {\n\tconst token = generateToken();\n\tconst expiresAt = Date.now() + TOKEN_TTL_MS;\n\n\t// Generate WebAuthn registration options\n\t// We need to generate a challenge here so it's available when the user visits the URL\n\tconst options = await generateRegistrationOptions({\n\t\trpName: \"Cirrus PDS\",\n\t\trpID: pdsHostname,\n\t\tuserName: did,\n\t\tuserDisplayName: name || did,\n\t\t// Require resident key (discoverable credential) for better UX\n\t\tauthenticatorSelection: {\n\t\t\tresidentKey: \"required\",\n\t\t\tuserVerification: \"preferred\",\n\t\t},\n\t\t// Don't require attestation (simpler, more compatible)\n\t\tattestationType: \"none\",\n\t});\n\n\t// Store the challenge with the token (and name for later)\n\tawait accountDO.rpcSavePasskeyToken(token, options.challenge, expiresAt, name);\n\n\tconst url = `https://${pdsHostname}/passkey/register?token=${token}`;\n\n\treturn {\n\t\ttoken,\n\t\turl,\n\t\texpiresAt,\n\t};\n}\n\n/**\n * Get registration options for a token\n *\n * Called when the user visits the registration URL.\n * Returns the WebAuthn options needed to start the ceremony.\n */\nexport async function getRegistrationOptions(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\tdid: string,\n\ttoken: string,\n): Promise<PublicKeyCredentialCreationOptionsJSON | null> {\n\t// Look up the token to get the challenge (but don't consume yet)\n\t// We need to re-consume on verification\n\tconst storage = await accountDO.rpcConsumePasskeyToken(token);\n\tif (!storage) {\n\t\treturn null;\n\t}\n\n\t// Re-save the token so it can be consumed during verification\n\t// (tokens are single-use, so we need to save it again)\n\tawait accountDO.rpcSavePasskeyToken(\n\t\ttoken,\n\t\tstorage.challenge,\n\t\tDate.now() + TOKEN_TTL_MS,\n\t\tstorage.name ?? undefined,\n\t);\n\n\t// Get existing passkeys to exclude them\n\tconst existingPasskeys = await accountDO.rpcListPasskeys();\n\n\t// Generate fresh options with the stored challenge\n\tconst options = await generateRegistrationOptions({\n\t\trpName: \"Cirrus PDS\",\n\t\trpID: pdsHostname,\n\t\tuserName: did,\n\t\tuserDisplayName: did,\n\t\tauthenticatorSelection: {\n\t\t\tresidentKey: \"required\",\n\t\t\tuserVerification: \"preferred\",\n\t\t},\n\t\tattestationType: \"none\",\n\t\texcludeCredentials: existingPasskeys.map((pk) => ({\n\t\t\tid: pk.credentialId,\n\t\t})),\n\t});\n\n\t// Override the challenge with our stored one\n\treturn {\n\t\t...options,\n\t\tchallenge: storage.challenge,\n\t};\n}\n\n/**\n * Complete passkey registration\n *\n * Verifies the registration response and stores the new passkey.\n * The name comes from the token (set during initPasskeyRegistration).\n */\nexport async function completePasskeyRegistration(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\ttoken: string,\n\tresponse: RegistrationResponseJSON,\n): Promise<{ success: true } | { success: false; error: string }> {\n\t// Consume the token to get the challenge and name\n\tconst tokenData = await accountDO.rpcConsumePasskeyToken(token);\n\tif (!tokenData) {\n\t\treturn { success: false, error: \"Invalid or expired token\" };\n\t}\n\n\ttry {\n\t\t// Verify the registration response\n\t\tconst verification = await verifyRegistrationResponse({\n\t\t\tresponse,\n\t\t\texpectedChallenge: tokenData.challenge,\n\t\t\texpectedOrigin: `https://${pdsHostname}`,\n\t\t\texpectedRPID: pdsHostname,\n\t\t});\n\n\t\tif (!verification.verified || !verification.registrationInfo) {\n\t\t\treturn { success: false, error: \"Verification failed\" };\n\t\t}\n\n\t\tconst { credential } = verification.registrationInfo;\n\n\t\t// Store the passkey (name comes from the token)\n\t\tawait accountDO.rpcSavePasskey(\n\t\t\tcredential.id,\n\t\t\tcredential.publicKey,\n\t\t\tcredential.counter,\n\t\t\ttokenData.name ?? undefined,\n\t\t);\n\n\t\treturn { success: true };\n\t} catch (err) {\n\t\tconsole.error(\"Passkey registration error:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: err instanceof Error ? err.message : \"Registration failed\",\n\t\t};\n\t}\n}\n\n/**\n * Generate authentication options for passkey login.\n * The challenge is stored server-side for later verification.\n */\nexport async function getAuthenticationOptions(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n): Promise<PublicKeyCredentialRequestOptionsJSON | null> {\n\t// Get all registered passkeys\n\tconst passkeys = await accountDO.rpcListPasskeys();\n\tif (passkeys.length === 0) {\n\t\treturn null;\n\t}\n\n\t// Explicitly list credential IDs - more compatible than discoverable credentials\n\tconst options = await generateAuthenticationOptions({\n\t\trpID: pdsHostname,\n\t\tuserVerification: \"preferred\",\n\t\tallowCredentials: passkeys.map((pk) => ({\n\t\t\tid: pk.credentialId,\n\t\t\t// Allow any transport type for maximum compatibility\n\t\t\ttransports: [\"internal\", \"hybrid\", \"usb\", \"ble\", \"nfc\"] as AuthenticatorTransport[],\n\t\t})),\n\t});\n\n\t// Store the challenge server-side for later verification\n\tawait accountDO.rpcSaveWebAuthnChallenge(options.challenge);\n\n\treturn options;\n}\n\n/**\n * Verify passkey authentication.\n * The challenge is validated against the server-stored value.\n */\nexport async function verifyPasskeyAuthentication(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\tresponse: AuthenticationResponseJSON,\n\tchallenge: string,\n): Promise<{ success: true } | { success: false; error: string }> {\n\ttry {\n\t\t// Verify the challenge was generated by the server and consume it (single-use)\n\t\tconst challengeValid = await accountDO.rpcConsumeWebAuthnChallenge(challenge);\n\t\tif (!challengeValid) {\n\t\t\treturn { success: false, error: \"Invalid or expired challenge\" };\n\t\t}\n\n\t\t// Get the passkey from storage\n\t\tconst passkey = await accountDO.rpcGetPasskey(response.id);\n\t\tif (!passkey) {\n\t\t\treturn { success: false, error: \"Unknown credential\" };\n\t\t}\n\n\t\t// Verify the authentication response\n\t\tconst verification = await verifyAuthenticationResponse({\n\t\t\tresponse,\n\t\t\texpectedChallenge: challenge,\n\t\t\texpectedOrigin: `https://${pdsHostname}`,\n\t\t\texpectedRPID: pdsHostname,\n\t\t\tcredential: {\n\t\t\t\tid: passkey.credentialId,\n\t\t\t\tpublicKey: new Uint8Array(passkey.publicKey),\n\t\t\t\tcounter: passkey.counter,\n\t\t\t},\n\t\t});\n\n\t\tif (!verification.verified) {\n\t\t\treturn { success: false, error: \"Verification failed\" };\n\t\t}\n\n\t\t// Update the counter\n\t\tawait accountDO.rpcUpdatePasskeyCounter(\n\t\t\tresponse.id,\n\t\t\tverification.authenticationInfo.newCounter,\n\t\t);\n\n\t\treturn { success: true };\n\t} catch (err) {\n\t\tconsole.error(\"Passkey authentication error:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: err instanceof Error ? err.message : \"Authentication failed\",\n\t\t};\n\t}\n}\n\n/**\n * List all passkeys for the account\n */\nexport async function listPasskeys(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<PasskeyInfo[]> {\n\tconst passkeys = await accountDO.rpcListPasskeys();\n\treturn passkeys.map((pk) => ({\n\t\tid: pk.credentialId,\n\t\tname: pk.name,\n\t\tcreatedAt: pk.createdAt,\n\t\tlastUsedAt: pk.lastUsedAt,\n\t}));\n}\n\n/**\n * Delete a passkey\n */\nexport async function deletePasskey(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tcredentialId: string,\n): Promise<boolean> {\n\treturn accountDO.rpcDeletePasskey(credentialId);\n}\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\";\nimport {\n\tgetAuthenticationOptions,\n\tverifyPasskeyAuthentication,\n\ttype AuthenticationResponseJSON,\n} from \"./passkey\";\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\t// Passkey authentication options\n\t\tgetPasskeyOptions: async (): Promise<Record<string, unknown> | null> => {\n\t\t\tconst options = await getAuthenticationOptions(accountDO, env.PDS_HOSTNAME);\n\t\t\treturn options as Record<string, unknown> | null;\n\t\t},\n\t\t// Passkey verification\n\t\tverifyPasskey: async (response, challenge: string) => {\n\t\t\tconst result = await verifyPasskeyAuthentication(\n\t\t\t\taccountDO,\n\t\t\t\tenv.PDS_HOSTNAME,\n\t\t\t\tresponse as AuthenticationResponseJSON,\n\t\t\t\tchallenge,\n\t\t\t);\n\t\t\tif (!result.success) 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\t// Messaging platform link preview bots pre-fetch URLs shared in DMs and\n\t\t// channels, which consumes the one-time PAR request URI before the user\n\t\t// can open it. Return a minimal HTML page for known preview bots instead\n\t\t// of processing the OAuth request. Only specific messaging platforms are\n\t\t// matched — generic crawlers and spiders should consume the token since\n\t\t// an unknown bot hitting an OAuth URL is legitimately suspicious.\n\t\tconst ua = c.req.header(\"User-Agent\") ?? \"\";\n\t\tif (\n\t\t\t/TelegramBot|Slackbot|Discordbot|Twitterbot|facebookexternalhit|WhatsApp/i.test(\n\t\t\t\tua,\n\t\t\t)\n\t\t) {\n\t\t\treturn c.html(`<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Cirrus Authorization</title>\n\t<meta name=\"description\" content=\"Cirrus PDS authorization page. Open this link in your browser to continue.\">\n\t<meta property=\"og:title\" content=\"Cirrus Authorization\">\n\t<meta property=\"og:description\" content=\"Open this link in your browser to continue.\">\n</head>\n<body>\n\t<p>Open this link in your browser to continue.</p>\n</body>\n</html>`);\n\t\t}\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// Passkey authentication endpoint\n\toauth.post(\"/oauth/passkey-auth\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handlePasskeyAuth(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// UserInfo endpoint (OpenID Connect)\n\t// Returns user claims for the authenticated user\n\toauth.get(\"/oauth/userinfo\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\tconst tokenData = await provider.verifyAccessToken(c.req.raw);\n\n\t\tif (!tokenData) {\n\t\t\treturn c.json(\n\t\t\t\t{ error: \"invalid_token\", error_description: \"Invalid or expired token\" },\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\t// Return OpenID Connect userinfo response\n\t\t// sub is required, we also include preferred_username (handle)\n\t\treturn c.json({\n\t\t\tsub: tokenData.sub,\n\t\t\tpreferred_username: c.env.HANDLE,\n\t\t});\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, TokenExpiredError } 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: \"com.atproto.access\" });\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 (err) {\n\t\t// Match official PDS: expired tokens return 400 with 'ExpiredToken'\n\t\t// This is required for clients to trigger automatic token refresh\n\t\tif (err instanceof TokenExpiredError) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\t\t// Session JWT verification failed for other reasons, 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 * Uses @atcute/identity-resolver which is already Workers-compatible\n * (uses redirect: \"manual\" internally).\n */\n\nimport {\n\tCompositeDidDocumentResolver,\n\tPlcDidDocumentResolver,\n\tWebDidDocumentResolver,\n} from \"@atcute/identity-resolver\";\nimport type { DidDocument } from \"@atcute/identity\";\nimport type { Did } from \"@atcute/lexicons/syntax\";\nimport type { DidCache } from \"./did-cache\";\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\n// Re-export DidDocument for consumers\nexport type { DidDocument };\n\n/**\n * Wrapper that always uses globalThis.fetch so it can be mocked in tests.\n * @atcute resolvers capture the fetch reference at construction time,\n * so we need this indirection to allow test mocking.\n */\nconst stubbableFetch: typeof fetch = (input, init) => globalThis.fetch(input, init);\n\nexport class DidResolver {\n\tprivate resolver: CompositeDidDocumentResolver<\"plc\" | \"web\">;\n\tprivate timeout: number;\n\tprivate cache?: DidCache;\n\n\tconstructor(opts: DidResolverOpts = {}) {\n\t\tthis.timeout = opts.timeout ?? TIMEOUT_MS;\n\t\tthis.cache = opts.didCache;\n\n\t\tthis.resolver = new CompositeDidDocumentResolver({\n\t\t\tmethods: {\n\t\t\t\tplc: new PlcDidDocumentResolver({\n\t\t\t\t\tapiUrl: opts.plcUrl ?? PLC_DIRECTORY,\n\t\t\t\t\tfetch: stubbableFetch,\n\t\t\t\t}),\n\t\t\t\tweb: new WebDidDocumentResolver({\n\t\t\t\t\tfetch: stubbableFetch,\n\t\t\t\t}),\n\t\t\t},\n\t\t});\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\t// Create abort signal with timeout\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n\t\ttry {\n\t\t\t// @atcute resolver throws on errors, we return null\n\t\t\tconst doc = await this.resolver.resolve(did as Did<\"plc\" | \"web\">, {\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t\t// Validate that the returned document matches the requested DID\n\t\t\tif (doc.id !== did) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn doc;\n\t\t} catch {\n\t\t\treturn null;\n\t\t} finally {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t}\n}\n","/**\n * DID cache using Cloudflare Workers Cache API\n */\n\nimport { defs, type DidDocument } from \"@atcute/identity\";\nimport { waitUntil } from \"cloudflare:workers\";\n\n/**\n * Cache result from checking the DID cache.\n */\nexport interface CacheResult {\n\tdid: string;\n\tdoc: DidDocument;\n\tupdatedAt: number;\n\tstale: boolean;\n\texpired: boolean;\n}\n\n/**\n * Interface for DID document caching.\n */\nexport interface DidCache {\n\tcacheDid(\n\t\tdid: string,\n\t\tdoc: DidDocument,\n\t\tprevResult?: CacheResult,\n\t): Promise<void>;\n\tcheckCache(did: string): Promise<CacheResult | null>;\n\trefreshCache(\n\t\tdid: string,\n\t\tgetDoc: () => Promise<DidDocument | null>,\n\t\tprevResult?: CacheResult,\n\t): Promise<void>;\n\tclearEntry(did: string): Promise<void>;\n\tclear(): Promise<void>;\n}\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\t// Validate cached document schema\n\t\tconst parsed = defs.didDocument.try(await response.json());\n\t\tif (!parsed.ok || parsed.value.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: parsed.value,\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 { getAtprotoServiceEndpoint } from \"@atcute/identity\";\nimport { createServiceJwt } from \"./service-auth\";\nimport { verifyAccessToken, TokenExpiredError } 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 = getAtprotoServiceEndpoint(didDoc, { id: serviceId as `#${string}` });\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 (err) {\n\t\t\t// Match official PDS: expired tokens return 400 with 'ExpiredToken'\n\t\t\t// This is required for clients to trigger automatic token refresh\n\t\t\tif (err instanceof TokenExpiredError) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"ExpiredToken\",\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\t// Other token verification errors - 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","/**\n * Detect content type from file magic bytes.\n * Returns the detected MIME type or null if unknown.\n */\nexport function detectContentType(bytes: Uint8Array): string | null {\n\t// MP4/M4V/MOV - check for ftyp box\n\tif (bytes.length >= 12) {\n\t\tconst ftyp = String.fromCharCode(\n\t\t\tbytes[4]!,\n\t\t\tbytes[5]!,\n\t\t\tbytes[6]!,\n\t\t\tbytes[7]!,\n\t\t);\n\t\tif (ftyp === \"ftyp\") {\n\t\t\t// Check brand for more specific type\n\t\t\tconst brand = String.fromCharCode(\n\t\t\t\tbytes[8]!,\n\t\t\t\tbytes[9]!,\n\t\t\t\tbytes[10]!,\n\t\t\t\tbytes[11]!,\n\t\t\t);\n\t\t\tif (\n\t\t\t\tbrand === \"isom\" ||\n\t\t\t\tbrand === \"iso2\" ||\n\t\t\t\tbrand === \"mp41\" ||\n\t\t\t\tbrand === \"mp42\" ||\n\t\t\t\tbrand === \"avc1\"\n\t\t\t) {\n\t\t\t\treturn \"video/mp4\";\n\t\t\t}\n\t\t\tif (brand === \"M4V \" || brand === \"M4VH\" || brand === \"M4VP\") {\n\t\t\t\treturn \"video/x-m4v\";\n\t\t\t}\n\t\t\tif (brand === \"qt \") {\n\t\t\t\treturn \"video/quicktime\";\n\t\t\t}\n\t\t\t// Default to mp4 for any ftyp\n\t\t\treturn \"video/mp4\";\n\t\t}\n\t}\n\n\t// JPEG\n\tif (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {\n\t\treturn \"image/jpeg\";\n\t}\n\n\t// PNG\n\tif (\n\t\tbytes[0] === 0x89 &&\n\t\tbytes[1] === 0x50 &&\n\t\tbytes[2] === 0x4e &&\n\t\tbytes[3] === 0x47\n\t) {\n\t\treturn \"image/png\";\n\t}\n\n\t// GIF\n\tif (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {\n\t\treturn \"image/gif\";\n\t}\n\n\t// WebP\n\tif (\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\treturn \"image/webp\";\n\t}\n\n\t// WebM\n\tif (\n\t\tbytes[0] === 0x1a &&\n\t\tbytes[1] === 0x45 &&\n\t\tbytes[2] === 0xdf &&\n\t\tbytes[3] === 0xa3\n\t) {\n\t\treturn \"video/webm\";\n\t}\n\n\treturn null;\n}\n","import type { Context } from \"hono\";\nimport { isDid, isNsid, isRecordKey } from \"@atcute/lexicons/syntax\";\nimport type { AccountDurableObject } from \"../account-do.js\";\nimport type { AppEnv } from \"../types.js\";\nimport { detectContentType } from \"../format.js\";\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\t// Determine content type, with fallback for missing or invalid values\n\tlet contentType = blob.httpMetadata?.contentType;\n\n\t// If no content type or invalid wildcard, try to detect from content\n\tif (!contentType || contentType === \"*/*\") {\n\t\t// Read first few bytes to detect content type\n\t\tconst [headerStream, bodyStream] = blob.body.tee();\n\t\tconst reader = headerStream.getReader();\n\t\tconst { value: headerBytes } = await reader.read();\n\t\treader.releaseLock();\n\n\t\tif (headerBytes && headerBytes.length >= 12) {\n\t\t\tcontentType =\n\t\t\t\tdetectContentType(headerBytes) || \"application/octet-stream\";\n\t\t} else {\n\t\t\tcontentType = \"application/octet-stream\";\n\t\t}\n\n\t\treturn new Response(bodyStream, {\n\t\t\tstatus: 200,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": contentType,\n\t\t\t\t\"Content-Length\": blob.size.toString(),\n\t\t\t},\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\": contentType,\n\t\t\t\"Content-Length\": blob.size.toString(),\n\t\t},\n\t});\n}\n\nexport async function getRecord(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\tconst collection = c.req.query(\"collection\");\n\tconst rkey = c.req.query(\"rkey\");\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 (!collection) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: collection\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (!rkey) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: rkey\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate collection is an NSID\n\tif (!isNsid(collection)) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Invalid collection format (must be NSID)\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate rkey format\n\tif (!isRecordKey(rkey)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid rkey format\" },\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Check if this is our DID\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\ttry {\n\t\tconst carBytes = await accountDO.rpcGetRecordProof(collection, rkey);\n\n\t\treturn new Response(carBytes, {\n\t\t\tstatus: 200,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/vnd.ipld.car\",\n\t\t\t\t\"Content-Length\": carBytes.length.toString(),\n\t\t\t},\n\t\t});\n\t} catch (err) {\n\t\t// The proof CAR will still be returned even if the record doesn't exist\n\t\t// (to prove non-existence), so errors here indicate storage issues\n\t\tconsole.error(\"Error getting record proof:\", err);\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: \"Failed to get record proof\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n","import { parse, ValidationError, type BaseSchema } from \"@atcute/lexicons/validations\";\n\n// Import record schemas from @atcute/bluesky\nimport {\n\tAppBskyActorProfile,\n\tAppBskyFeedGenerator,\n\tAppBskyFeedLike,\n\tAppBskyFeedPost,\n\tAppBskyFeedPostgate,\n\tAppBskyFeedRepost,\n\tAppBskyFeedThreadgate,\n\tAppBskyGraphBlock,\n\tAppBskyGraphFollow,\n\tAppBskyGraphList,\n\tAppBskyGraphListblock,\n\tAppBskyGraphListitem,\n\tAppBskyGraphStarterpack,\n\tAppBskyGraphVerification,\n\tAppBskyLabelerService,\n} from \"@atcute/bluesky\";\n\n/**\n * Map of collection NSID to validation schema.\n * Only includes record types that can be created in repositories.\n */\nconst recordSchemas: Record<string, BaseSchema> = {\n\t\"app.bsky.actor.profile\": AppBskyActorProfile.mainSchema,\n\t\"app.bsky.feed.generator\": AppBskyFeedGenerator.mainSchema,\n\t\"app.bsky.feed.like\": AppBskyFeedLike.mainSchema,\n\t\"app.bsky.feed.post\": AppBskyFeedPost.mainSchema,\n\t\"app.bsky.feed.postgate\": AppBskyFeedPostgate.mainSchema,\n\t\"app.bsky.feed.repost\": AppBskyFeedRepost.mainSchema,\n\t\"app.bsky.feed.threadgate\": AppBskyFeedThreadgate.mainSchema,\n\t\"app.bsky.graph.block\": AppBskyGraphBlock.mainSchema,\n\t\"app.bsky.graph.follow\": AppBskyGraphFollow.mainSchema,\n\t\"app.bsky.graph.list\": AppBskyGraphList.mainSchema,\n\t\"app.bsky.graph.listblock\": AppBskyGraphListblock.mainSchema,\n\t\"app.bsky.graph.listitem\": AppBskyGraphListitem.mainSchema,\n\t\"app.bsky.graph.starterpack\": AppBskyGraphStarterpack.mainSchema,\n\t\"app.bsky.graph.verification\": AppBskyGraphVerification.mainSchema,\n\t\"app.bsky.labeler.service\": AppBskyLabelerService.mainSchema,\n};\n\n/**\n * Record validator for AT Protocol records.\n *\n * Validates records against official Bluesky lexicon schemas from @atcute/bluesky.\n * Uses optimistic validation strategy:\n * - If a 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.\n */\nexport class RecordValidator {\n\tprivate strictMode: boolean;\n\n\tconstructor(options: { strict?: boolean } = {}) {\n\t\tthis.strictMode = options.strict ?? false;\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\tconst schema = recordSchemas[collection];\n\n\t\tif (!schema) {\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\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tparse(schema, record);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ValidationError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Lexicon validation failed for ${collection}: ${error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\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\treturn collection in recordSchemas;\n\t}\n\n\t/**\n\t * Get list of all loaded schema NSIDs.\n\t */\n\tgetLoadedSchemas(): string[] {\n\t\treturn Object.keys(recordSchemas);\n\t}\n}\n\n/**\n * Shared validator instance (singleton pattern).\n * Uses optimistic validation by default (strict: false).\n */\nexport const validator = new RecordValidator({ strict: false });\n","import type { Context } from \"hono\";\nimport { isDid } from \"@atcute/lexicons/syntax\";\nimport { AccountDurableObject } from \"../account-do.js\";\nimport type { AppEnv, AuthedAppEnv } from \"../types.js\";\nimport { validator } from \"../validation.js\";\nimport { detectContentType } from \"../format.js\";\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\tif (!isDid(repo)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(repo)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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: `at://${repo}/${collection}/${rkey}`,\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\tif (!isDid(repo)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tlet contentType = c.req.header(\"Content-Type\");\n\n\tconst bytes = new Uint8Array(await c.req.arrayBuffer());\n\tif (!contentType || contentType === \"*/*\") {\n\t\tcontentType = detectContentType(bytes) || \"application/octet-stream\";\n\t}\n\n\t// Size limit check (60MB)\n\tconst MAX_BLOB_SIZE = 60 * 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\tTokenExpiredError,\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(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): 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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\tconst email = storedEmail || c.env.EMAIL;\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\t...(email ? { email } : {}),\n\t\temailConfirmed: true,\n\t\tactive: true,\n\t});\n}\n\n/**\n * Refresh a session\n */\nexport async function refreshSession(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): 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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\t\tconst email = storedEmail || c.env.EMAIL;\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\t...(email ? { email } : {}),\n\t\t\temailConfirmed: true,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\t// Match official PDS: expired tokens return 'ExpiredToken', other errors return 'InvalidToken'\n\t\tif (err instanceof TokenExpiredError) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\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 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(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): 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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\t\tconst email = storedEmail || c.env.EMAIL;\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\t...(email ? { email } : {}),\n\t\t\temailConfirmed: true,\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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\t\tconst email = storedEmail || c.env.EMAIL;\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\t...(email ? { email } : {}),\n\t\t\temailConfirmed: true,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\t// Match official PDS: expired tokens return 400 with 'ExpiredToken'\n\t\t// This is required for clients to trigger automatic token refresh\n\t\tif (err instanceof TokenExpiredError) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\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 checkAccountStatus(\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\t// Account is considered \"activated\" if it's currently active OR has content\n\t\tconst activated = active || indexedRecords > 0;\n\n\t\treturn c.json({\n\t\t\tactivated,\n\t\t\tactive,\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\tactivated: false,\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 * Request a token to update the account email.\n * Single-user PDS: no token needed, always returns tokenRequired: false.\n */\nexport async function requestEmailUpdate(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\treturn c.json({ tokenRequired: false });\n}\n\n/**\n * Request email confirmation.\n * Single-user PDS: email is always confirmed, nothing to do.\n */\nexport async function requestEmailConfirmation(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\treturn c.json({});\n}\n\n/**\n * Update the account email address\n */\nexport async function updateEmail(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json<{ email: string }>();\n\n\tif (!body.email) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required field: email\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tawait accountDO.rpcUpdateEmail(body.email);\n\treturn c.json({});\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","/**\n * Stateless migration tokens for outbound account migration\n *\n * Uses HMAC-SHA256 to create tokens that encode the DID and expiry time.\n * No database storage required - validity is verified by the signature.\n *\n * Token format: base64url(payload).base64url(signature)\n * Payload: {\"did\":\"did:plc:xxx\",\"exp\":1736600000}\n *\n * Tokens expire after 15 minutes - enough time to complete the migration\n * process but short enough to limit exposure if the token is leaked.\n */\n\nimport { base64url } from \"jose\";\n\nconst MINUTE = 60 * 1000;\nconst TOKEN_EXPIRY = 15 * MINUTE; // 15 minutes\n\ninterface TokenPayload {\n\tdid: string;\n\texp: number;\n}\n\n/**\n * Create an HMAC-SHA256 signature\n */\nasync function hmacSign(data: string, secret: string): Promise<ArrayBuffer> {\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"sign\"],\n\t);\n\treturn crypto.subtle.sign(\"HMAC\", key, encoder.encode(data));\n}\n\n/**\n * Verify an HMAC-SHA256 signature\n */\nasync function hmacVerify(\n\tdata: string,\n\tsignature: ArrayBuffer,\n\tsecret: string,\n): Promise<boolean> {\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\treturn crypto.subtle.verify(\"HMAC\", key, signature, encoder.encode(data));\n}\n\n\n/**\n * Create a migration token for outbound migration\n *\n * @param did - The user's DID\n * @param jwtSecret - The JWT_SECRET used for signing\n * @returns A stateless, signed token\n */\nexport async function createMigrationToken(\n\tdid: string,\n\tjwtSecret: string,\n): Promise<string> {\n\tconst exp = Math.floor((Date.now() + TOKEN_EXPIRY) / 1000);\n\tconst payload: TokenPayload = { did, exp };\n\n\tconst payloadStr = JSON.stringify(payload);\n\tconst payloadB64 = base64url.encode(new TextEncoder().encode(payloadStr));\n\n\tconst signature = await hmacSign(payloadB64, jwtSecret);\n\tconst signatureB64 = base64url.encode(new Uint8Array(signature));\n\n\treturn `${payloadB64}.${signatureB64}`;\n}\n\n/**\n * Validate a migration token\n *\n * @param token - The token to validate\n * @param expectedDid - The expected DID (must match token payload)\n * @param jwtSecret - The JWT_SECRET used for verification\n * @returns The payload if valid, null if invalid/expired\n */\nexport async function validateMigrationToken(\n\ttoken: string,\n\texpectedDid: string,\n\tjwtSecret: string,\n): Promise<TokenPayload | null> {\n\tconst parts = token.split(\".\");\n\tif (parts.length !== 2) {\n\t\treturn null;\n\t}\n\n\tconst [payloadB64, signatureB64] = parts as [string, string];\n\n\ttry {\n\t\t// Verify signature\n\t\tconst signatureBytes = base64url.decode(signatureB64);\n\t\tconst isValid = await hmacVerify(payloadB64, signatureBytes.buffer, jwtSecret);\n\t\tif (!isValid) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Decode and validate payload\n\t\tconst payloadStr = new TextDecoder().decode(base64url.decode(payloadB64));\n\t\tconst payload: TokenPayload = JSON.parse(payloadStr);\n\n\t\t// Check DID matches\n\t\tif (payload.did !== expectedDid) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check expiry\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tif (payload.exp < now) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn payload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n","/**\n * Identity XRPC endpoints for outbound migration\n *\n * These endpoints allow migrating FROM Cirrus to another PDS.\n *\n * Flow:\n * 1. New PDS calls requestPlcOperationSignature (after user authenticates)\n * 2. We generate a migration token (stateless HMAC)\n * 3. User runs `pds migrate-token` CLI to get the token\n * 4. User enters token into new PDS\n * 5. New PDS calls signPlcOperation with token + new endpoint/key\n * 6. We validate token and return signed PLC operation\n * 7. New PDS submits operation to PLC directory\n */\nimport type { Context } from \"hono\";\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { encode } from \"@atcute/cbor\";\nimport { base64url } from \"jose\";\nimport type { AuthedAppEnv } from \"../types\";\nimport { createMigrationToken, validateMigrationToken } from \"../migration-token\";\n\nconst PLC_DIRECTORY = \"https://plc.directory\";\n\n/**\n * PLC operation structure\n */\ninterface UnsignedPlcOperation {\n\ttype: \"plc_operation\";\n\tprev: string | null;\n\trotationKeys: string[];\n\tverificationMethods: Record<string, string>;\n\talsoKnownAs: string[];\n\tservices: Record<string, { type: string; endpoint: string }>;\n}\n\ninterface SignedPlcOperation extends UnsignedPlcOperation {\n\tsig: string;\n}\n\n/**\n * Audit log entry from plc.directory\n */\ninterface PlcAuditLog {\n\tdid: string;\n\toperation: SignedPlcOperation;\n\tcid: string;\n\tnullified: boolean;\n\tcreatedAt: string;\n}\n\n/**\n * Request a PLC operation signature for outbound migration.\n *\n * In Bluesky's implementation, this sends an email with a token.\n * In Cirrus, we're single-user with no email, so we just return success.\n * The user gets the token via `pds migrate-token` CLI.\n *\n * Endpoint: POST com.atproto.identity.requestPlcOperationSignature\n */\nexport async function requestPlcOperationSignature(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\t// For Cirrus, we don't send emails - the user gets the token via CLI.\n\t// Just return success to indicate the request was accepted.\n\t// The token is generated on-demand when the user runs `pds migrate-token`.\n\treturn new Response(null, { status: 200 });\n}\n\n/**\n * Sign a PLC operation for migrating to a new PDS.\n *\n * Validates the migration token and returns a signed PLC operation\n * that updates the DID document to point to the new PDS.\n *\n * Endpoint: POST com.atproto.identity.signPlcOperation\n */\nexport async function signPlcOperation(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\tconst body = await c.req.json<{\n\t\ttoken?: string;\n\t\trotationKeys?: string[];\n\t\talsoKnownAs?: string[];\n\t\tverificationMethods?: Record<string, string>;\n\t\tservices?: Record<string, { type: string; endpoint: string }>;\n\t}>();\n\n\tconst { token } = body;\n\n\tif (!token) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: token\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate the migration token\n\tconst payload = await validateMigrationToken(\n\t\ttoken,\n\t\tc.env.DID,\n\t\tc.env.JWT_SECRET,\n\t);\n\n\tif (!payload) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidToken\",\n\t\t\t\tmessage: \"Invalid or expired migration token\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Get current PLC state to build the update\n\tconst currentOp = await getLatestPlcOperation(c.env.DID);\n\tif (!currentOp) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: \"Could not fetch current PLC state\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n\n\t// Build the new operation, merging current state with requested changes\n\tconst newOp: UnsignedPlcOperation = {\n\t\ttype: \"plc_operation\",\n\t\tprev: currentOp.cid,\n\t\trotationKeys: body.rotationKeys ?? currentOp.operation.rotationKeys,\n\t\talsoKnownAs: body.alsoKnownAs ?? currentOp.operation.alsoKnownAs,\n\t\tverificationMethods:\n\t\t\tbody.verificationMethods ?? currentOp.operation.verificationMethods,\n\t\tservices: body.services ?? currentOp.operation.services,\n\t};\n\n\t// Sign the operation with our signing key\n\tconst keypair = await Secp256k1Keypair.import(c.env.SIGNING_KEY);\n\tconst signedOp = await signOperation(newOp, keypair);\n\n\treturn c.json({ operation: signedOp });\n}\n\n/**\n * Get the latest PLC operation for a DID\n */\nasync function getLatestPlcOperation(\n\tdid: string,\n): Promise<PlcAuditLog | null> {\n\ttry {\n\t\tconst res = await fetch(`${PLC_DIRECTORY}/${did}/log/audit`);\n\t\tif (!res.ok) {\n\t\t\treturn null;\n\t\t}\n\t\tconst log = (await res.json()) as PlcAuditLog[];\n\t\t// Return the most recent non-nullified operation\n\t\treturn log.filter((op) => !op.nullified).pop() ?? null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Sign a PLC operation with the given keypair\n *\n * PLC operations are signed by:\n * 1. CBOR-encoding the unsigned operation\n * 2. Signing the bytes with secp256k1\n * 3. Adding the signature as base64url\n */\nasync function signOperation(\n\top: UnsignedPlcOperation,\n\tkeypair: Secp256k1Keypair,\n): Promise<SignedPlcOperation> {\n\t// CBOR-encode the operation (without sig field)\n\tconst bytes = encode(op);\n\n\t// Sign the bytes\n\tconst sig = await keypair.sign(bytes);\n\n\t// Convert signature to base64url\n\treturn {\n\t\t...op,\n\t\tsig: base64url.encode(sig),\n\t};\n}\n\n/**\n * Generate a migration token for the CLI.\n *\n * This endpoint allows the CLI to generate a token that can be used\n * to complete an outbound migration without requiring the secret\n * to be available client-side.\n *\n * Endpoint: GET gg.mk.experimental.getMigrationToken\n */\nexport async function getMigrationToken(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\tconst token = await createMigrationToken(c.env.DID, c.env.JWT_SECRET);\n\treturn c.json({ token });\n}\n","/**\n * Passkey Registration UI\n *\n * Renders the HTML page for passkey registration.\n * Matches the styling of the OAuth consent UI.\n */\n\nimport type { PublicKeyCredentialCreationOptionsJSON } from \"./passkey\";\n\n/**\n * The main registration script (static, can be hashed).\n * Dynamic data is passed via data attributes on the script element.\n */\nconst PASSKEY_REGISTRATION_SCRIPT = `\n// Get dynamic data from script element\nconst scriptEl = document.currentScript;\nconst options = JSON.parse(scriptEl.dataset.options);\nconst token = scriptEl.dataset.token;\n\n// Convert base64url challenge to ArrayBuffer\nfunction base64urlToBuffer(base64url) {\n\tconst base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n\tconst padding = '='.repeat((4 - base64.length % 4) % 4);\n\tconst binary = atob(base64 + padding);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes.buffer;\n}\n\n// Convert ArrayBuffer to base64url\nfunction bufferToBase64url(buffer) {\n\tconst bytes = new Uint8Array(buffer);\n\tlet binary = '';\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i]);\n\t}\n\treturn btoa(binary)\n\t\t.replace(/\\\\+/g, '-')\n\t\t.replace(/\\\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nasync function registerPasskey() {\n\tconst btn = document.getElementById('register-btn');\n\tconst status = document.getElementById('status');\n\n\tbtn.disabled = true;\n\tbtn.textContent = 'Registering...';\n\tstatus.textContent = '';\n\tstatus.className = 'status';\n\n\ttry {\n\t\t// Convert options for WebAuthn API\n\t\tconst publicKeyOptions = {\n\t\t\tchallenge: base64urlToBuffer(options.challenge),\n\t\t\trp: options.rp,\n\t\t\tuser: {\n\t\t\t\tid: base64urlToBuffer(options.user.id),\n\t\t\t\tname: options.user.name,\n\t\t\t\tdisplayName: options.user.displayName,\n\t\t\t},\n\t\t\tpubKeyCredParams: options.pubKeyCredParams,\n\t\t\ttimeout: options.timeout,\n\t\t\tattestation: options.attestation,\n\t\t\tauthenticatorSelection: options.authenticatorSelection,\n\t\t\texcludeCredentials: (options.excludeCredentials || []).map(cred => ({\n\t\t\t\tid: base64urlToBuffer(cred.id),\n\t\t\t\ttype: cred.type,\n\t\t\t\ttransports: cred.transports,\n\t\t\t})),\n\t\t};\n\n\t\t// Perform WebAuthn ceremony\n\t\tconst credential = await navigator.credentials.create({\n\t\t\tpublicKey: publicKeyOptions\n\t\t});\n\n\t\tif (!credential) {\n\t\t\tthrow new Error('No credential returned');\n\t\t}\n\n\t\t// Prepare response for server\n\t\tconst response = {\n\t\t\tid: credential.id,\n\t\t\trawId: bufferToBase64url(credential.rawId),\n\t\t\tresponse: {\n\t\t\t\tclientDataJSON: bufferToBase64url(credential.response.clientDataJSON),\n\t\t\t\tattestationObject: bufferToBase64url(credential.response.attestationObject),\n\t\t\t\ttransports: credential.response.getTransports ? credential.response.getTransports() : [],\n\t\t\t},\n\t\t\ttype: credential.type,\n\t\t\tclientExtensionResults: credential.getClientExtensionResults(),\n\t\t\tauthenticatorAttachment: credential.authenticatorAttachment,\n\t\t};\n\n\t\t// Submit to server\n\t\tconst result = await fetch('/passkey/register', {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t\tbody: JSON.stringify({ token, response }),\n\t\t});\n\n\t\tconst data = await result.json();\n\n\t\tif (data.success) {\n\t\t\t// Show success UI\n\t\t\tdocument.getElementById('register-container').style.display = 'none';\n\t\t\tdocument.getElementById('success-container').style.display = 'block';\n\t\t} else {\n\t\t\tthrow new Error(data.error || 'Registration failed');\n\t\t}\n\t} catch (err) {\n\t\tconsole.error('Registration error:', err);\n\t\tstatus.textContent = err.message || 'Registration failed. Please try again.';\n\t\tstatus.className = 'status error';\n\t\tbtn.disabled = false;\n\t\tbtn.textContent = 'Register Passkey';\n\t}\n}\n\n// Check if WebAuthn is supported\nif (!window.PublicKeyCredential) {\n\tdocument.getElementById('status').textContent = 'WebAuthn is not supported in this browser.';\n\tdocument.getElementById('status').className = 'status error';\n\tdocument.getElementById('register-btn').disabled = true;\n} else {\n\tdocument.getElementById('register-btn').addEventListener('click', registerPasskey);\n}\n`;\n\n/**\n * Compute SHA-256 hash for CSP script-src\n */\nasync function computeScriptHash(script: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(script);\n\tconst hashBuffer = await crypto.subtle.digest(\"SHA-256\", data);\n\tconst hashArray = Array.from(new Uint8Array(hashBuffer));\n\tconst base64Hash = btoa(String.fromCharCode(...hashArray));\n\treturn `'sha256-${base64Hash}'`;\n}\n\n// Pre-computed hash (computed at module load, will be a Promise)\nlet registrationScriptHashPromise: Promise<string> | null = null;\n\n/**\n * Get the script hash for the passkey registration script\n */\nasync function getPasskeyScriptHash(): Promise<string> {\n\tif (!registrationScriptHashPromise) {\n\t\tregistrationScriptHashPromise = computeScriptHash(\n\t\t\tPASSKEY_REGISTRATION_SCRIPT,\n\t\t);\n\t}\n\treturn registrationScriptHashPromise;\n}\n\n/**\n * Content Security Policy for the passkey UI (computed dynamically with script hash)\n */\nexport async function getPasskeyUiCsp(): Promise<string> {\n\tconst scriptHash = await getPasskeyScriptHash();\n\treturn `default-src 'none'; script-src ${scriptHash}; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'`;\n}\n\n/**\n * Content Security Policy for error pages (no scripts)\n */\nexport const PASSKEY_ERROR_CSP =\n\t\"default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'\";\n\n/**\n * Escape HTML to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\");\n}\n\nexport interface PasskeyUIOptions {\n\t/** WebAuthn registration options */\n\toptions: PublicKeyCredentialCreationOptionsJSON;\n\t/** Token for the registration */\n\ttoken: string;\n\t/** User's handle */\n\thandle: string;\n}\n\n/**\n * Render the passkey registration page\n */\nexport function renderPasskeyRegistrationPage(opts: PasskeyUIOptions): string {\n\tconst { options, token, handle } = opts;\n\n\t// Serialize options for data attribute (HTML-escaped JSON)\n\tconst optionsAttr = escapeHtml(JSON.stringify(options));\n\tconst tokenAttr = escapeHtml(token);\n\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Register Passkey</title>\n\t<style>\n\t\t* {\n\t\t\tbox-sizing: border-box;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n\n\t\tbody {\n\t\t\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n\t\t\tbackground: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n\t\t\tmin-height: 100vh;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tpadding: 20px;\n\t\t\tcolor: #e0e0e0;\n\t\t}\n\n\t\t.container {\n\t\t\tbackground: #1e1e30;\n\t\t\tborder-radius: 16px;\n\t\t\tpadding: 32px;\n\t\t\tmax-width: 400px;\n\t\t\twidth: 100%;\n\t\t\tbox-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n\t\t\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t.icon {\n\t\t\twidth: 64px;\n\t\t\theight: 64px;\n\t\t\tborder-radius: 12px;\n\t\t\tmargin: 0 auto 16px;\n\t\t\tbackground: linear-gradient(135deg, #3b82f6, #8b5cf6);\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tfont-size: 28px;\n\t\t}\n\n\t\th1 {\n\t\t\tfont-size: 20px;\n\t\t\tfont-weight: 600;\n\t\t\tmargin-bottom: 8px;\n\t\t}\n\n\t\t.handle {\n\t\t\tfont-size: 14px;\n\t\t\tcolor: #60a5fa;\n\t\t\tmargin-bottom: 24px;\n\t\t}\n\n\t\t.info {\n\t\t\tbackground: rgba(255, 255, 255, 0.05);\n\t\t\tborder-radius: 12px;\n\t\t\tpadding: 16px;\n\t\t\tmargin-bottom: 24px;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: #9ca3af;\n\t\t\ttext-align: left;\n\t\t}\n\n\t\t.info p {\n\t\t\tmargin-bottom: 8px;\n\t\t}\n\n\t\t.info p:last-child {\n\t\t\tmargin-bottom: 0;\n\t\t}\n\n\t\t.btn {\n\t\t\twidth: 100%;\n\t\t\tpadding: 14px 24px;\n\t\t\tborder-radius: 8px;\n\t\t\tfont-size: 16px;\n\t\t\tfont-weight: 500;\n\t\t\tcursor: pointer;\n\t\t\ttransition: all 0.2s;\n\t\t\tborder: none;\n\t\t\tbackground: linear-gradient(135deg, #3b82f6, #2563eb);\n\t\t\tcolor: white;\n\t\t}\n\n\t\t.btn:hover:not(:disabled) {\n\t\t\tbackground: linear-gradient(135deg, #2563eb, #1d4ed8);\n\t\t}\n\n\t\t.btn:disabled {\n\t\t\topacity: 0.5;\n\t\t\tcursor: not-allowed;\n\t\t}\n\n\t\t.status {\n\t\t\tmargin-top: 16px;\n\t\t\tfont-size: 14px;\n\t\t\tmin-height: 20px;\n\t\t}\n\n\t\t.status.error {\n\t\t\tcolor: #f87171;\n\t\t}\n\n\t\t.status.success {\n\t\t\tcolor: #22c55e;\n\t\t}\n\n\t\t.success-container {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\t.success-icon {\n\t\t\twidth: 64px;\n\t\t\theight: 64px;\n\t\t\tborder-radius: 50%;\n\t\t\tmargin: 0 auto 16px;\n\t\t\tbackground: rgba(34, 197, 94, 0.1);\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tfont-size: 32px;\n\t\t\tcolor: #22c55e;\n\t\t}\n\n\t\t.close-info {\n\t\t\tmargin-top: 24px;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: #6b7280;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"container\" id=\"register-container\">\n\t\t<div class=\"icon\">🔐</div>\n\t\t<h1>Register Passkey</h1>\n\t\t<p class=\"handle\">@${escapeHtml(handle)}</p>\n\n\t\t<div class=\"info\">\n\t\t\t<p>A passkey lets you sign in securely using your device's biometrics (Face ID, fingerprint) or PIN.</p>\n\t\t\t<p>Click the button below to create a passkey for this device.</p>\n\t\t</div>\n\n\t\t<button class=\"btn\" id=\"register-btn\">\n\t\t\tRegister Passkey\n\t\t</button>\n\n\t\t<div class=\"status\" id=\"status\"></div>\n\t</div>\n\n\t<div class=\"container success-container\" id=\"success-container\">\n\t\t<div class=\"success-icon\">✓</div>\n\t\t<h1>Passkey Registered!</h1>\n\t\t<p class=\"handle\">@${escapeHtml(handle)}</p>\n\n\t\t<div class=\"info\">\n\t\t\t<p>Your passkey has been registered successfully.</p>\n\t\t\t<p>You can now use it to sign in to your account.</p>\n\t\t</div>\n\n\t\t<p class=\"close-info\">You can close this window.</p>\n\t</div>\n\n\t<script data-options=\"${optionsAttr}\" data-token=\"${tokenAttr}\">${PASSKEY_REGISTRATION_SCRIPT}</script>\n</body>\n</html>`;\n}\n\n/**\n * Render an error page\n */\nexport function renderPasskeyErrorPage(\n\terror: string,\n\tdescription: string,\n): string {\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Passkey Error</title>\n\t<style>\n\t\t* {\n\t\t\tbox-sizing: border-box;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n\n\t\tbody {\n\t\t\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n\t\t\tbackground: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n\t\t\tmin-height: 100vh;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tpadding: 20px;\n\t\t\tcolor: #e0e0e0;\n\t\t}\n\n\t\t.container {\n\t\t\tbackground: #1e1e30;\n\t\t\tborder-radius: 16px;\n\t\t\tpadding: 32px;\n\t\t\tmax-width: 400px;\n\t\t\twidth: 100%;\n\t\t\tbox-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n\t\t\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t.error-icon {\n\t\t\twidth: 64px;\n\t\t\theight: 64px;\n\t\t\tbackground: rgba(239, 68, 68, 0.1);\n\t\t\tborder-radius: 50%;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tmargin: 0 auto 16px;\n\t\t\tfont-size: 32px;\n\t\t}\n\n\t\th1 {\n\t\t\tfont-size: 20px;\n\t\t\tmargin-bottom: 8px;\n\t\t\tcolor: #f87171;\n\t\t}\n\n\t\tp {\n\t\t\tcolor: #9ca3af;\n\t\t\tfont-size: 14px;\n\t\t}\n\n\t\tcode {\n\t\t\tbackground: rgba(255, 255, 255, 0.1);\n\t\t\tpadding: 2px 6px;\n\t\t\tborder-radius: 4px;\n\t\t\tfont-size: 12px;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"container\">\n\t\t<div class=\"error-icon\">!</div>\n\t\t<h1>Passkey Error</h1>\n\t\t<p>${escapeHtml(description)}</p>\n\t\t<p style=\"margin-top: 8px;\"><code>${escapeHtml(error)}</code></p>\n\t</div>\n</body>\n</html>`;\n}\n","","// Public API\nexport { AccountDurableObject } from \"./account-do\";\nexport type { PDSEnv, DataLocation } 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 { isDid, isHandle } from \"@atcute/lexicons/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 * as identity from \"./xrpc/identity\";\nimport * as passkey from \"./passkey\";\nimport {\n\trenderPasskeyRegistrationPage,\n\trenderPasskeyErrorPage,\n\tgetPasskeyUiCsp,\n\tPASSKEY_ERROR_CSP,\n} from \"./passkey-ui\";\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\nif (!isDid(env.DID)) {\n\tthrow new Error(`Invalid DID format: ${env.DID}`);\n}\nif (!isHandle(env.HANDLE)) {\n\tthrow new Error(`Invalid handle format: ${env.HANDLE}`);\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 with optional data location\nfunction getAccountDO(env: PDSEnv) {\n\tconst location = env.DATA_LOCATION;\n\n\t// \"eu\" is a jurisdiction (hard guarantee), everything else is a hint (best-effort)\n\tif (location === \"eu\") {\n\t\tconst namespace = env.ACCOUNT.jurisdiction(\"eu\");\n\t\treturn namespace.get(namespace.idFromName(\"account\"));\n\t}\n\n\t// Location hints (or \"auto\"/undefined = no constraint)\n\tconst id = env.ACCOUNT.idFromName(\"account\");\n\tif (location && location !== \"auto\") {\n\t\treturn env.ACCOUNT.get(id, { locationHint: location });\n\t}\n\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 - AT Protocol standard path\napp.get(\"/xrpc/_health\", async (c) => {\n\ttry {\n\t\tconst accountDO = getAccountDO(c.env);\n\t\tawait accountDO.rpcHealthCheck();\n\t\treturn c.json({ status: \"ok\", version });\n\t} catch {\n\t\treturn c.json({ status: \"unhealthy\", version }, 503);\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);\napp.get(\"/xrpc/com.atproto.sync.getRecord\", (c) =>\n\tsync.getRecord(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 - handle local repo directly, proxy foreign DIDs to AppView\napp.use(\"/xrpc/com.atproto.repo.describeRepo\", async (c, next) => {\n\tconst requestedRepo = c.req.query(\"repo\");\n\tif (!requestedRepo || requestedRepo === c.env.DID) {\n\t\treturn repo.describeRepo(c, getAccountDO(c.env));\n\t}\n\tawait next();\n});\n\napp.use(\"/xrpc/com.atproto.repo.getRecord\", async (c, next) => {\n\tconst requestedRepo = c.req.query(\"repo\");\n\tif (!requestedRepo || requestedRepo === c.env.DID) {\n\t\treturn repo.getRecord(c, getAccountDO(c.env));\n\t}\n\tawait next();\n});\n\napp.use(\"/xrpc/com.atproto.repo.listRecords\", async (c, next) => {\n\tconst requestedRepo = c.req.query(\"repo\");\n\tif (!requestedRepo || requestedRepo === c.env.DID) {\n\t\treturn repo.listRecords(c, getAccountDO(c.env));\n\t}\n\tawait next();\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// Identity management for outbound migration\n// These endpoints allow migrating FROM Cirrus to another PDS\napp.post(\n\t\"/xrpc/com.atproto.identity.requestPlcOperationSignature\",\n\trequireAuth,\n\tidentity.requestPlcOperationSignature,\n);\napp.post(\n\t\"/xrpc/com.atproto.identity.signPlcOperation\",\n\trequireAuth,\n\tidentity.signPlcOperation,\n);\napp.get(\n\t\"/xrpc/gg.mk.experimental.getMigrationToken\",\n\trequireAuth,\n\tidentity.getMigrationToken,\n);\n\n// Session management\napp.post(\"/xrpc/com.atproto.server.createSession\", (c) =>\n\tserver.createSession(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.refreshSession\", (c) =>\n\tserver.refreshSession(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.server.getSession\", (c) =>\n\tserver.getSession(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.deleteSession\", server.deleteSession);\n\n// Account lifecycle\napp.get(\"/xrpc/com.atproto.server.checkAccountStatus\", requireAuth, (c) =>\n\tserver.checkAccountStatus(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);\napp.post(\n\t\"/xrpc/com.atproto.server.requestEmailUpdate\",\n\trequireAuth,\n\tserver.requestEmailUpdate,\n);\napp.post(\n\t\"/xrpc/com.atproto.server.requestEmailConfirmation\",\n\trequireAuth,\n\tserver.requestEmailConfirmation,\n);\napp.post(\"/xrpc/com.atproto.server.updateEmail\", requireAuth, (c) =>\n\tserver.updateEmail(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// Emit identity event to refresh handle verification with relays\napp.post(\"/xrpc/gg.mk.experimental.emitIdentityEvent\", 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// Firehose status (authenticated)\napp.get(\n\t\"/xrpc/gg.mk.experimental.getFirehoseStatus\",\n\trequireAuth,\n\tasync (c) => {\n\t\tconst accountDO = getAccountDO(c.env);\n\t\treturn c.json(await accountDO.rpcGetFirehoseStatus());\n\t},\n);\n\n// ============================================\n// Passkey Routes\n// ============================================\n\n// Initialize passkey registration (authenticated)\napp.post(\"/passkey/init\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst body = await c.req.json<{ name?: string }>().catch(() => ({} as { name?: string }));\n\ttry {\n\t\tconst result = await passkey.initPasskeyRegistration(\n\t\t\taccountDO,\n\t\t\tc.env.PDS_HOSTNAME,\n\t\t\tc.env.DID,\n\t\t\tbody.name,\n\t\t);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconsole.error(\"Passkey init error:\", err);\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn c.json({ error: \"PasskeyInitFailed\", message }, 500);\n\t}\n});\n\n// Passkey registration page (GET - renders UI)\napp.get(\"/passkey/register\", async (c) => {\n\tconst token = c.req.query(\"token\");\n\tif (!token) {\n\t\treturn c.html(\n\t\t\trenderPasskeyErrorPage(\"missing_token\", \"No registration token provided.\"),\n\t\t\t400,\n\t\t\t{ \"Content-Security-Policy\": PASSKEY_ERROR_CSP },\n\t\t);\n\t}\n\n\tconst accountDO = getAccountDO(c.env);\n\tconst options = await passkey.getRegistrationOptions(\n\t\taccountDO,\n\t\tc.env.PDS_HOSTNAME,\n\t\tc.env.DID,\n\t\ttoken,\n\t);\n\n\tif (!options) {\n\t\treturn c.html(\n\t\t\trenderPasskeyErrorPage(\"invalid_token\", \"Invalid or expired registration token.\"),\n\t\t\t400,\n\t\t\t{ \"Content-Security-Policy\": PASSKEY_ERROR_CSP },\n\t\t);\n\t}\n\n\tconst csp = await getPasskeyUiCsp();\n\treturn c.html(\n\t\trenderPasskeyRegistrationPage({\n\t\t\toptions,\n\t\t\ttoken,\n\t\t\thandle: c.env.HANDLE,\n\t\t}),\n\t\t200,\n\t\t{ \"Content-Security-Policy\": csp },\n\t);\n});\n\n// Complete passkey registration (POST - receives WebAuthn response)\napp.post(\"/passkey/register\", async (c) => {\n\tconst body = await c.req.json<{\n\t\ttoken: string;\n\t\tresponse: any;\n\t}>();\n\n\tif (!body.token || !body.response) {\n\t\treturn c.json({ success: false, error: \"Missing token or response\" }, 400);\n\t}\n\n\tconst accountDO = getAccountDO(c.env);\n\t// Name comes from the token (set during init)\n\tconst result = await passkey.completePasskeyRegistration(\n\t\taccountDO,\n\t\tc.env.PDS_HOSTNAME,\n\t\tbody.token,\n\t\tbody.response,\n\t);\n\n\tif (result.success) {\n\t\treturn c.json({ success: true });\n\t} else {\n\t\treturn c.json({ success: false, error: result.error }, 400);\n\t}\n});\n\n// List passkeys (authenticated)\napp.get(\"/passkey/list\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst passkeys = await passkey.listPasskeys(accountDO);\n\treturn c.json({ passkeys });\n});\n\n// Delete passkey (authenticated)\napp.post(\"/passkey/delete\", requireAuth, async (c) => {\n\tconst body = await c.req.json<{ id: string }>();\n\tif (!body.id) {\n\t\treturn c.json({ success: false, error: \"Missing passkey ID\" }, 400);\n\t}\n\n\tconst accountDO = getAccountDO(c.env);\n\tconst deleted = await passkey.deletePasskey(accountDO, body.id);\n\treturn c.json({ success: deleted });\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,aAAa,OAA8B;AACnD,KAAI,UAAU,QAAQ,OAAO,UAAU,SACtC,QAAO;CAER,MAAM,MAAM;AACZ,QAAO,WAAW,OAAO,IAAI,OAAO,iBAAiB;;;;;AAMtD,SAAS,oBAAoB,KAAmB;AAC/C,QAAO,UAAU,WAAW,IAAI,UAAU,CAAC,CAAC;;;;;AAM7C,SAAS,qBAAqB,OAAyB;AACtD,KAAI,UAAU,QAAQ,UAAU,OAC/B,QAAO;AAGR,KAAI,OAAO,UAAU,SACpB,QAAO;AAIR,KAAI,YAAY,OAAO,MAAM,IAAI,iBAAiB,WACjD,QAAO,QAAQ,MAAM;AAItB,KAAI,aAAa,MAAM,CACtB,QAAO,oBAAoB,MAAM;AAIlC,KAAI,MAAM,QAAQ,MAAM,CACvB,QAAO,MAAM,IAAI,qBAAqB;CAIvC,MAAM,MAAM;AACZ,KAAI,IAAI,gBAAgB,QAAQ;EAC/B,MAAMA,SAAkC,EAAE;AAC1C,OAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,IAAI,CAC3C,QAAO,OAAO,qBAAqB,IAAI;AAExC,SAAO;;AAGR,QAAO;;;;;;;;AASR,SAAgBC,SAAO,OAA4B;AAElD,QAAOC,OADW,qBAAqB,MAAM,CACf;;;;;AAM/B,SAAS,yBAAyB,OAAyB;AAC1D,KAAI,UAAU,QAAQ,UAAU,OAC/B,QAAO;AAGR,KAAI,OAAO,UAAU,SACpB,QAAO;AAIR,KAAI,QAAQ,MAAM,CACjB,QAAO,UAAU,MAAM;AAMxB,KAAI,MAAM,QAAQ,MAAM,CACvB,QAAO,MAAM,IAAI,yBAAyB;CAI3C,MAAM,MAAM;AACZ,KAAI,IAAI,gBAAgB,QAAQ;EAC/B,MAAMF,SAAkC,EAAE;AAC1C,OAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,IAAI,CAC3C,QAAO,OAAO,yBAAyB,IAAI;AAE5C,SAAO;;AAGR,QAAO;;;;;;;;AASR,SAAgBG,SAAO,OAA4B;AAElD,QAAO,yBADSC,OAAa,MAAM,CACK;;;;;;;;;;;AC7HzC,IAAa,oBAAb,cACS,mBAET;CACC,YAAY,AAAQC,KAAiB;AACpC,SAAO;EADY;;;;;;CAQpB,WAAW,gBAAyB,MAAY;AAC/C,OAAK,IAAI,KAAK;;;;;;;;;;;;;;;;;;;;;;+BAsBe,gBAAgB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4DjD;AAGF,MAAI;AACH,QAAK,IAAI,KAAK,+CAA+C;UACtD;;;;;CAQT,MAAM,UAA+B;EACpC,MAAM,OAAO,KAAK,IAChB,KAAK,+CAA+C,CACpD,SAAS;AACX,MAAI,KAAK,WAAW,KAAK,CAAC,KAAK,IAAI,SAClC,QAAO;AAER,SAAO,IAAI,MAAM,KAAK,GAAI,SAAmB;;;;;CAM9C,MAAM,SAAiC;EACtC,MAAM,OAAO,KAAK,IAChB,KAAK,0CAA0C,CAC/C,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,OAAkB,OAAQ;;;;;CAM/D,MAAM,SAA0B;EAC/B,MAAM,OAAO,KAAK,IAChB,KAAK,0CAA0C,CAC/C,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,OAAkB,IAAK;;;;;CAM5D,MAAM,UAA2B;AAChC,OAAK,IAAI,KAAK,mDAAmD;AACjE,SAAO,KAAK,QAAQ;;;;;CAMrB,MAAM,SAAS,KAAsC;EACpD,MAAM,OAAO,KAAK,IAChB,KAAK,0CAA0C,IAAI,UAAU,CAAC,CAC9D,SAAS;AACX,MAAI,KAAK,WAAW,KAAK,CAAC,KAAK,IAAI,MAClC,QAAO;AAGR,SAAO,IAAI,WAAW,KAAK,GAAI,MAAqB;;;;;CAMrD,MAAM,IAAI,KAA4B;AAIrC,SAHa,KAAK,IAChB,KAAK,8CAA8C,IAAI,UAAU,CAAC,CAClE,SAAS,CACC,SAAS;;;;;CAMtB,MAAM,UAAU,MAA4D;EAC3E,MAAM,SAAS,IAAI,UAAU;EAC7B,MAAMC,UAAiB,EAAE;AAEzB,OAAK,MAAM,OAAO,MAAM;GACvB,MAAM,QAAQ,MAAM,KAAK,SAAS,IAAI;AACtC,OAAI,MACH,QAAO,IAAI,KAAK,MAAM;OAEtB,SAAQ,KAAK,IAAI;;AAInB,SAAO;GAAE;GAAQ;GAAS;;;;;CAM3B,MAAM,SAAS,KAAU,OAAmB,KAA4B;AACvE,OAAK,IAAI,KACR,oEACA,IAAI,UAAU,EACd,OACA,IACA;;;;;CAMF,MAAM,QAAQ,QAAkB,KAA4B;EAG3D,MAAM,cAAe,OACnB;AACF,MAAI,YACH,MAAK,MAAM,CAAC,QAAQ,UAAU,YAC7B,MAAK,IAAI,KACR,oEACA,QACA,OACA,IACA;;;;;CAQJ,MAAM,WAAW,KAAU,KAA4B;AACtD,OAAK,IAAI,KACR,4DACA,IAAI,UAAU,EACd,IACA;;;;;CAMF,MAAM,YAAY,QAAmC;EAKpD,MAAM,cACL,OAAO,UACN;AACF,MAAI,YACH,MAAK,MAAM,CAAC,QAAQ,UAAU,YAC7B,MAAK,IAAI,KACR,oEACA,QACA,OACA,OAAO,IACP;EAKH,MAAM,aAAc,OAAO,YACzB;AACF,MAAI,WACH,MAAK,MAAM,UAAU,WACpB,MAAK,IAAI,KAAK,oCAAoC,OAAO;AAK3D,QAAM,KAAK,WAAW,OAAO,KAAK,OAAO,IAAI;;;;;CAM9C,MAAM,cAA+B;EACpC,MAAM,OAAO,KAAK,IAChB,KAAK,iDAAiD,CACtD,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,SAAoB,IAAK;;;;;CAM9D,MAAM,UAAyB;AAC9B,OAAK,IAAI,KAAK,qBAAqB;AACnC,OAAK,IAAI,KACR,iEACA;;;;;CAMF,MAAM,cAA+B;EACpC,MAAM,OAAO,KAAK,IAChB,KAAK,uCAAuC,CAC5C,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,SAAoB,IAAK;;;;;CAM9D,MAAM,iBAAqC;EAC1C,MAAM,OAAO,KAAK,IAChB,KAAK,4CAA4C,CACjD,SAAS;AACX,MAAI,KAAK,WAAW,KAAK,CAAC,KAAK,IAAI,KAClC,QAAO,EAAE;EAEV,MAAM,OAAO,KAAK,GAAI;AACtB,MAAI;AACH,UAAO,KAAK,MAAM,KAAK;UAChB;AACP,UAAO,EAAE;;;;;;CAOX,MAAM,eAAe,aAAuC;EAC3D,MAAM,OAAO,KAAK,UAAU,YAAY;AACxC,OAAK,IAAI,KAAK,gDAAgD,KAAK;;;;;CAMpE,MAAM,YAA8B;EACnC,MAAM,OAAO,KAAK,IAChB,KAAK,6CAA6C,CAClD,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,WAAsB,IAAK;;;;;CAMhE,MAAM,UAAU,QAAgC;AAC/C,OAAK,IAAI,KACR,iDACA,SAAS,IAAI,EACb;;;;;CAMF,WAA0B;EACzB,MAAM,OAAO,KAAK,IAChB,KAAK,4CAA4C,CACjD,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,SAAoB,OAAQ;;;;;CAMjE,SAAS,OAAqB;AAC7B,OAAK,IAAI,KAAK,gDAAgD,MAAM;;;;;CAUrE,iBAA2B;AAI1B,SAHa,KAAK,IAChB,KAAK,yDAAyD,CAC9D,SAAS,CACC,KAAK,QAAQ,IAAI,WAAqB;;;;;CAMnD,cAAc,YAA0B;AACvC,OAAK,IAAI,KACR,6DACA,WACA;;;;;CAMF,iBAA0B;AAIzB,SAHa,KAAK,IAChB,KAAK,oCAAoC,CACzC,SAAS,CACC,SAAS;;;;;CAUtB,cAAc,WAAmB,SAAuB;AACvD,OAAK,IAAI,KACR,wEACA,WACA,QACA;;;;;CAMF,eAAe,WAAmB,UAA0B;AAC3D,OAAK,MAAM,OAAO,SACjB,MAAK,cAAc,WAAW,IAAI;;;;;CAOpC,kBAAkB,WAAyB;AAC1C,OAAK,IAAI,KAAK,+CAA+C,UAAU;;;;;CAMxE,kBAAkB,KAAa,MAAc,UAAwB;AACpE,OAAK,IAAI,KACR,gFACA,KACA,MACA,SACA;;;;;CAMF,eAAe,KAAsB;AAIpC,SAHa,KAAK,IAChB,KAAK,sDAAsD,IAAI,CAC/D,SAAS,CACC,SAAS;;;;;CAMtB,qBAA6B;EAC5B,MAAM,OAAO,KAAK,IAChB,KAAK,2DAA2D,CAChE,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,SAAoB,IAAK;;;;;CAM9D,qBAA6B;EAC5B,MAAM,OAAO,KAAK,IAChB,KAAK,+CAA+C,CACpD,SAAS;AACX,SAAO,KAAK,SAAS,IAAM,KAAK,GAAI,SAAoB,IAAK;;;;;CAM9D,iBACC,QAAgB,KAChB,QACwE;EACxE,MAAMC,QAAmD,EAAE;EAG3D,MAAM,QAAQ,SACX;;;;gBAKA;;;;;EAMH,MAAM,OAAO,SACV,KAAK,IAAI,KAAK,OAAO,QAAQ,QAAQ,EAAE,CAAC,SAAS,GACjD,KAAK,IAAI,KAAK,OAAO,QAAQ,EAAE,CAAC,SAAS;AAE5C,OAAK,MAAM,OAAO,KAAK,MAAM,GAAG,MAAM,CACrC,OAAM,KAAK;GACV,KAAK,IAAI;GACT,WAAW,IAAI;GACf,CAAC;AAMH,SAAO;GAAE;GAAO,QAHA,KAAK,SAAS,QACD,MAAM,MAAM,SAAS,IAAI,MAAM;GAExB;;;;;CAMrC,oBAA0B;AACzB,OAAK,IAAI,KAAK,0BAA0B;AACxC,OAAK,IAAI,KAAK,6BAA6B;;;;;CAU5C,YACC,cACA,WACA,SACA,MACO;AACP,OAAK,IAAI,KACR;0BAEA,cACA,WACA,SACA,QAAQ,KACR;;;;;CAMF,WAAW,cAOF;EACR,MAAM,OAAO,KAAK,IAChB,KACA;6CAEA,aACA,CACA,SAAS;AAEX,MAAI,KAAK,WAAW,EAAG,QAAO;EAE9B,MAAM,MAAM,KAAK;AACjB,SAAO;GACN,cAAc,IAAI;GAClB,WAAW,IAAI,WAAW,IAAI,WAA0B;GACxD,SAAS,IAAI;GACb,MAAM,IAAI;GACV,WAAW,IAAI;GACf,YAAY,IAAI;GAChB;;;;;CAMF,eAKG;AAQF,SAPa,KAAK,IAChB,KACA;6CAEA,CACA,SAAS,CAEC,KAAK,SAAS;GACzB,cAAc,IAAI;GAClB,MAAM,IAAI;GACV,WAAW,IAAI;GACf,YAAY,IAAI;GAChB,EAAE;;;;;CAMJ,cAAc,cAA+B;EAC5C,MAAM,SAAS,KAAK,IAAI,KAAK,qCAAqC,CAAC,KAAK;AACxE,OAAK,IAAI,KAAK,gDAAgD,aAAa;EAC3E,MAAM,QAAQ,KAAK,IAAI,KAAK,qCAAqC,CAAC,KAAK;AACvE,SAAQ,OAAO,IAAgB,MAAM;;;;;CAMtC,qBAAqB,cAAsB,SAAuB;AACjE,OAAK,IAAI,KACR;8BAEA,SACA,aACA;;;;;CAMF,cAAuB;AAEtB,SADe,KAAK,IAAI,KAAK,qCAAqC,CAAC,KAAK,CACzD,IAAe;;;;;CAU/B,iBAAiB,OAAe,WAAmB,WAAmB,MAAqB;AAC1F,OAAK,IAAI,KACR,uFACA,OACA,WACA,WACA,QAAQ,KACR;;;;;CAMF,oBAAoB,OAAkE;EACrF,MAAM,OAAO,KAAK,IAChB,KACA,0EACA,MACA,CACA,SAAS;AAEX,MAAI,KAAK,WAAW,EAAG,QAAO;EAE9B,MAAM,MAAM,KAAK;EACjB,MAAM,YAAY,IAAI;AAGtB,OAAK,IAAI,KAAK,8CAA8C,MAAM;AAGlE,MAAI,KAAK,KAAK,GAAG,UAAW,QAAO;AAEnC,SAAO;GAAE,WAAW,IAAI;GAAqB,MAAO,IAAI,QAAmB;GAAM;;;;;CAMlF,uBAA6B;AAC5B,OAAK,IAAI,KACR,mDACA,KAAK,KAAK,CACV;;;;;;;;;;;;AC5pBH,IAAa,qBAAb,MAAwD;CACvD,YAAY,AAAQC,KAAiB;EAAjB;;;;;CAKpB,aAAmB;AAClB,OAAK,IAAI,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmEZ;;;;;CAMH,UAAgB;EACf,MAAMC,QAAM,KAAK,KAAK;AACtB,OAAK,IAAI,KAAK,qDAAqDA,MAAI;AACvE,OAAK,IAAI,KACR,iEACAA,MACA;AACD,OAAK,IAAI,KAAK,uDAAuDA,MAAI;EAEzE,MAAM,cAAcA,QAAM,MAAS;AACnC,OAAK,IAAI,KAAK,iDAAiD,YAAY;EAE3E,MAAM,kBAAkBA,QAAM,MAAS;AACvC,OAAK,IAAI,KACR,8DACA,gBACA;;CAOF,MAAM,aAAa,MAAc,MAAmC;AACnE,OAAK,IAAI,KACR;;qCAGA,MACA,KAAK,UACL,KAAK,aACL,KAAK,eACL,KAAK,qBACL,KAAK,OACL,KAAK,KACL,KAAK,UACL;;CAGF,MAAM,YAAY,MAA4C;EAC7D,MAAM,OAAO,KAAK,IAChB,KACA;2CAEA,KACA,CACA,SAAS;AAEX,MAAI,KAAK,WAAW,EAAG,QAAO;EAE9B,MAAM,MAAM,KAAK;EACjB,MAAM,YAAY,IAAI;AAEtB,MAAI,KAAK,KAAK,GAAG,WAAW;AAC3B,QAAK,IAAI,KAAK,+CAA+C,KAAK;AAClE,UAAO;;AAGR,SAAO;GACN,UAAU,IAAI;GACd,aAAa,IAAI;GACjB,eAAe,IAAI;GACnB,qBAAqB,IAAI;GACzB,OAAO,IAAI;GACX,KAAK,IAAI;GACT;GACA;;CAGF,MAAM,eAAe,MAA6B;AACjD,OAAK,IAAI,KAAK,+CAA+C,KAAK;;CAOnE,MAAM,WAAW,MAAgC;AAChD,OAAK,IAAI,KACR;;wCAGA,KAAK,aACL,KAAK,cACL,KAAK,UACL,KAAK,KACL,KAAK,OACL,KAAK,WAAW,MAChB,KAAK,UACL,KAAK,WACL,KAAK,UAAU,IAAI,EACnB;;CAGF,MAAM,iBAAiB,aAAgD;EACtE,MAAM,OAAO,KAAK,IAChB,KACA;+CAEA,YACA,CACA,SAAS;AAEX,MAAI,KAAK,WAAW,EAAG,QAAO;EAE9B,MAAM,MAAM,KAAK;EACjB,MAAM,UAAU,QAAQ,IAAI,QAAQ;EACpC,MAAM,YAAY,IAAI;AAEtB,MAAI,WAAW,KAAK,KAAK,GAAG,UAC3B,QAAO;AAGR,SAAO;GACN,aAAa,IAAI;GACjB,cAAc,IAAI;GAClB,UAAU,IAAI;GACd,KAAK,IAAI;GACT,OAAO,IAAI;GACX,SAAU,IAAI,YAAuB;GACrC,UAAU,IAAI;GACd;GACA;GACA;;CAGF,MAAM,kBAAkB,cAAiD;EACxE,MAAM,OAAO,KAAK,IAChB,KACA;gDAEA,aACA,CACA,SAAS;AAEX,MAAI,KAAK,WAAW,EAAG,QAAO;EAE9B,MAAM,MAAM,KAAK;EACjB,MAAM,UAAU,QAAQ,IAAI,QAAQ;AAEpC,MAAI,QAAS,QAAO;AAEpB,SAAO;GACN,aAAa,IAAI;GACjB,cAAc,IAAI;GAClB,UAAU,IAAI;GACd,KAAK,IAAI;GACT,OAAO,IAAI;GACX,SAAU,IAAI,YAAuB;GACrC,UAAU,IAAI;GACd,WAAW,IAAI;GACf;GACA;;CAGF,MAAM,YAAY,aAAoC;AACrD,OAAK,IAAI,KACR,8DACA,YACA;;CAGF,MAAM,gBAAgB,KAA4B;AACjD,OAAK,IAAI,KAAK,qDAAqD,IAAI;;CAOxE,MAAM,WAAW,UAAkB,UAAyC;AAC3E,OAAK,IAAI,KACR;;+BAGA,UACA,SAAS,YACT,KAAK,UAAU,SAAS,aAAa,EACrC,SAAS,WAAW,MACpB,SAAS,aAAa,MACtB,SAAS,YAAY,KAAK,KAAK,CAC/B;;CAGF,MAAM,UAAU,UAAkD;EACjE,MAAM,OAAO,KAAK,IAChB,KACA;6CAEA,SACA,CACA,SAAS;AAEX,MAAI,KAAK,WAAW,EAAG,QAAO;EAE9B,MAAM,MAAM,KAAK;AACjB,SAAO;GACN,UAAU,IAAI;GACd,YAAY,IAAI;GAChB,cAAc,KAAK,MAAM,IAAI,cAAwB;GACrD,SAAU,IAAI,YAAuB;GACrC,WAAY,IAAI,cAAyB;GACzC,UAAU,IAAI;GACd;;CAOF,MAAM,QAAQ,YAAoB,MAA8B;AAC/D,OAAK,IAAI,KACR;yBAEA,YACA,KAAK,UACL,KAAK,UAAU,KAAK,OAAO,EAC3B,KAAK,UACL;;CAGF,MAAM,OAAO,YAA6C;EACzD,MAAM,OAAO,KAAK,IAChB,KACA,sFACA,WACA,CACA,SAAS;AAEX,MAAI,KAAK,WAAW,EAAG,QAAO;EAE9B,MAAM,MAAM,KAAK;EACjB,MAAM,YAAY,IAAI;AAEtB,MAAI,KAAK,KAAK,GAAG,WAAW;AAC3B,QAAK,IAAI,KACR,wDACA,WACA;AACD,UAAO;;AAGR,SAAO;GACN,UAAU,IAAI;GACd,QAAQ,KAAK,MAAM,IAAI,OAAiB;GACxC;GACA;;CAGF,MAAM,UAAU,YAAmC;AAClD,OAAK,IAAI,KACR,wDACA,WACA;;CAOF,MAAM,kBAAkB,OAAiC;AAMxD,MAJa,KAAK,IAChB,KAAK,sDAAsD,MAAM,CACjE,SAAS,CAEF,SAAS,EACjB,QAAO;AAIR,OAAK,IAAI,KACR,8DACA,OACA,KAAK,KAAK,CACV;AAED,SAAO;;;;;CAMR,UAAgB;AACf,OAAK,IAAI,KAAK,+BAA+B;AAC7C,OAAK,IAAI,KAAK,2BAA2B;AACzC,OAAK,IAAI,KAAK,4BAA4B;AAC1C,OAAK,IAAI,KAAK,iCAAiC;AAC/C,OAAK,IAAI,KAAK,2BAA2B;AACzC,OAAK,IAAI,KAAK,wCAAwC;;;;;CAUvD,sBAAsB,WAAyB;AAC9C,OAAK,IAAI,KACR,+EACA,WACA,KAAK,KAAK,CACV;;;;;;CAOF,yBAAyB,WAA4B;EAEpD,MAAM,gBAAgB,KAAK,KAAK,GAAG,MAAS;AAS5C,MARa,KAAK,IAChB,KACA,0FACA,WACA,cACA,CACA,SAAS,CAEF,WAAW,EACnB,QAAO;AAIR,OAAK,IAAI,KACR,6DACA,UACA;AAED,SAAO;;;;;;;;;;;;;;ACpVT,IAAa,YAAb,MAAuB;CACtB,YAAY,AAAQC,KAAiB;EAAjB;;;;;;CAMpB,MAAM,eAAe,MAAqC;EAEzD,MAAM,WAAW,MAAM,gBAAgB,KAAK,QAAQ,KAAK,UAAU;EACnE,MAAM,wBAAO,IAAI,MAAM,EAAC,aAAa;EAGrC,MAAMC,eAAyC;GAC9C,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,KAAK,KAAK;GACV,OAAO,KAAK;GACZ,QAAQ;GACR,KAAK,KAAK,IAAI,KACZ,QAAgB;IAChB,QAAQ,GAAG;IACX,MAAM,GAAG,GAAG,WAAW,GAAG,GAAG;IAC7B,KAAM,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM;IACvC,EACD;GACD,QAAQ;GACR,QAAQ,SAAS,SAAS;GAC1B,OAAO,EAAE;GACT;GACA;EAID,MAAM,UAAUC,SAAW,aAAa;EAUxC,MAAM,MATS,KAAK,IAClB,KACA;;uBAGA,QACA,CACA,KAAK,CAEY;AAEnB,SAAO;GACN;GACA,MAAM;GACN,OAAO;IACN,GAAG;IACH;IACA;GACD;GACA;;;;;;;CAQF,MAAM,eAAe,QAAgB,QAAQ,KAA0B;EACtE,MAAM,OAAO,KAAK,IAChB,KACA;;;;iBAKA,QACA,MACA,CACA,SAAS;EAEX,MAAMC,SAAqB,EAAE;AAE7B,OAAK,MAAM,OAAO,MAAM;GACvB,MAAM,YAAY,IAAI;GACtB,MAAM,UAAU,IAAI,WAAW,IAAI,QAAuB;GAC1D,MAAM,MAAM,IAAI;GAChB,MAAM,OAAO,IAAI;AAEjB,OAAI,cAAc,YAAY;AAE7B,QAAI,QAAQ,WAAW,EACtB;IAGD,MAAM,UAAUC,SAAW,QAAQ;AACnC,WAAO,KAAK;KACX;KACA,MAAM;KACN,OAAO;MAAE,GAAG;MAAS;MAAK;KAC1B;KACA,CAAC;UACI;IAEN,MAAM,UAAUA,SAAW,QAAQ;AACnC,WAAO,KAAK;KACX;KACA,MAAM;KACN,OAAO;MAAE,GAAG;MAAS;MAAK;KAC1B;KACA,CAAC;;;AAIJ,SAAO;;;;;;CAOR,eAAuB;AAItB,SAHe,KAAK,IAClB,KAAK,8CAA8C,CACnD,KAAK,EACS,OAAkB;;;;;;CAOnC,MAAM,eAAe,YAAY,KAAsB;AACtD,OAAK,IAAI,KACR;gEAEA,UACA;;;;;;;;;;AC5MH,IAAa,YAAb,MAAuB;CACtB,YACC,AAAQC,IACR,AAAQC,KACP;EAFO;EACA;;;;;CAMT,MAAM,QAAQ,OAAmB,UAAoC;EAGpE,MAAM,SAASC,SADA,MAAMC,OAAU,WAAW,MAAM,CACd;EAGlC,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG;AAC3B,QAAM,KAAK,GAAG,IAAI,KAAK,OAAO,EAC7B,cAAc,EAAE,aAAa,UAAU,EACvC,CAAC;AAEF,SAAO;GACN,OAAO;GACP,KAAK,EAAE,OAAO,QAAQ;GACtB;GACA,MAAM,MAAM;GACZ;;;;;CAMF,MAAM,QAAQ,KAA2C;EACxD,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG;AAC3B,SAAO,KAAK,GAAG,IAAI,IAAI;;;;;CAMxB,MAAM,QAAQ,KAA+B;EAC5C,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG;AAE3B,SADa,MAAM,KAAK,GAAG,KAAK,IAAI,KACpB;;;;;;;;;;;;;;;ACdlB,IAAa,uBAAb,cAA0C,cAAsB;CAC/D,AAAQ,UAAoC;CAC5C,AAAQ,eAA0C;CAClD,AAAQ,OAAoB;CAC5B,AAAQ,UAAmC;CAC3C,AAAQ,YAA8B;CACtC,AAAQ,YAA8B;CACtC,AAAQ,qBAAqB;CAC7B,AAAQ,kBAAkB;CAE1B,YAAY,KAAyB,OAAa;AACjD,QAAM,KAAKC,MAAI;AAGf,MAAI,CAACA,MAAI,YACR,OAAM,IAAI,MAAM,qDAAqD;AAEtE,MAAI,CAACA,MAAI,IACR,OAAM,IAAI,MAAM,6CAA6C;AAI9D,MAAIA,MAAI,MACP,MAAK,YAAY,IAAI,UAAUA,MAAI,OAAOA,MAAI,IAAI;;;;;CAOpD,MAAc,2BAA0C;AACvD,MAAI,CAAC,KAAK,mBACT,OAAM,KAAK,IAAI,sBAAsB,YAAY;AAChD,OAAI,KAAK,mBAAoB;GAG7B,MAAM,gBACL,KAAK,IAAI,mBAAmB,UAC5B,KAAK,IAAI,mBAAmB,UAC5B,KAAK,IAAI,mBAAmB;AAE7B,QAAK,UAAU,IAAI,kBAAkB,KAAK,IAAI,QAAQ,IAAI;AAC1D,QAAK,QAAQ,WAAW,cAAc;AACtC,QAAK,eAAe,IAAI,mBAAmB,KAAK,IAAI,QAAQ,IAAI;AAChE,QAAK,aAAa,YAAY;AAC9B,QAAK,YAAY,IAAI,UAAU,KAAK,IAAI,QAAQ,IAAI;AACpD,QAAK,qBAAqB;AAG1B,QAAK,YAAY;AAIjB,OADqB,MAAM,KAAK,IAAI,QAAQ,UAAU,KACjC,KACpB,OAAM,KAAK,IAAI,QAAQ,SAAS,KAAK,KAAK,GAAG,MAAS;IAEtD;;;;;CAOJ,AAAQ,aAAmB;AAC1B,MAAI,KAAK,QACR,MAAK,QAAQ,sBAAsB;AAEpC,MAAI,KAAK,aACR,MAAK,aAAa,SAAS;;;;;;CAQ7B,MAAe,QAAuB;AACrC,QAAM,KAAK,0BAA0B;AAGrC,OAAK,YAAY;AAGjB,QAAM,KAAK,IAAI,QAAQ,SAAS,KAAK,KAAK,GAAG,MAAS;;;;;CAMvD,MAAc,wBAAuC;AACpD,QAAM,KAAK,0BAA0B;AAErC,MAAI,CAAC,KAAK,gBACT,OAAM,KAAK,IAAI,sBAAsB,YAAY;AAChD,OAAI,KAAK,gBAAiB;AAG1B,QAAK,UAAU,MAAM,iBAAiB,OAAO,KAAK,IAAI,YAAY;GAGlE,MAAM,OAAO,MAAM,KAAK,QAAS,SAAS;AAC1C,OAAI,KACH,MAAK,OAAO,MAAM,KAAK,KAAK,KAAK,SAAU,KAAK;OAEhD,MAAK,OAAO,MAAM,KAAK,OACtB,KAAK,SACL,KAAK,IAAI,KACT,KAAK,QACL;AAGF,QAAK,kBAAkB;IACtB;;;;;CAOJ,MAAM,aAAyC;AAC9C,QAAM,KAAK,0BAA0B;AACrC,SAAO,KAAK;;;;;CAMb,MAAM,kBAA+C;AACpD,QAAM,KAAK,0BAA0B;AACrC,SAAO,KAAK;;;;;CAMb,MAAM,UAAyB;AAC9B,QAAM,KAAK,uBAAuB;AAClC,SAAO,KAAK;;;;;CAMb,MAAM,eAA8B;AAGnC,MAAI,CADa,OADD,MAAM,KAAK,YAAY,EACR,WAAW,CAEzC,OAAM,IAAI,MACT,qFACA;;;;;CAOH,MAAM,aAAwC;AAC7C,QAAM,KAAK,uBAAuB;AAClC,SAAO,KAAK;;;;;CAMb,MAAM,QAAQ,MAA2B;AACxC,OAAK,OAAO;;;;;CAMb,MAAM,kBAIH;EACF,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,MAAM,UAAU,MAAM,KAAK,YAAY;AAGvC,MAAI,CAAC,QAAQ,gBAAgB,IAAK,MAAM,QAAQ,SAAS,EAAG;GAC3D,MAAM,uBAAO,IAAI,KAAa;AAC9B,cAAW,MAAM,UAAU,KAAK,aAAa,CAC5C,KAAI,CAAC,KAAK,IAAI,OAAO,WAAW,EAAE;AACjC,SAAK,IAAI,OAAO,WAAW;AAC3B,YAAQ,cAAc,OAAO,WAAW;;;AAK3C,SAAO;GACN,KAAK,KAAK;GACV,aAAa,QAAQ,gBAAgB;GACrC,KAAK,KAAK,IAAI,UAAU;GACxB;;;;;CAMF,MAAM,aACL,YACA,MAIS;EACT,MAAM,OAAO,MAAM,KAAK,SAAS;EAGjC,MAAM,UAAU,GAAG,WAAW,GAAG;EACjC,MAAM,YAAY,MAAM,KAAK,KAAK,IAAI,QAAQ;AAC9C,MAAI,CAAC,UACJ,QAAO;EAGR,MAAM,SAAS,MAAM,KAAK,UAAU,YAAY,KAAK;AAErD,MAAI,CAAC,OACJ,QAAO;AAGR,SAAO;GACN,KAAK,UAAU,UAAU;GACzB,QAAQ,gBAAgB,OAAO;GAC/B;;;;;CAMF,MAAM,eACL,YACA,MAQE;EACF,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,MAAM,UAAU,EAAE;EAClB,MAAM,YAAY,KAAK,UAAU,GAAG,WAAW;AAE/C,aAAW,MAAM,UAAU,KAAK,YAAY,UAAU,EAAE;AACvD,OAAI,OAAO,eAAe,YAAY;AACrC,QAAI,QAAQ,SAAS,EAAG;AACxB;;AAGD,WAAQ,KAAK;IACZ,KAAK,QAAQ,KAAK,IAAI,GAAG,OAAO,WAAW,GAAG,OAAO;IACrD,KAAK,OAAO,IAAI,UAAU;IAC1B,OAAO,gBAAgB,OAAO,OAAO;IACrC,CAAC;AAEF,OAAI,QAAQ,UAAU,KAAK,QAAQ,EAAG;;AAGvC,MAAI,KAAK,QACR,SAAQ,SAAS;EAGlB,MAAM,UAAU,QAAQ,SAAS,KAAK;EACtC,MAAM,UAAU,UAAU,QAAQ,MAAM,GAAG,KAAK,MAAM,GAAG;AAKzD,SAAO;GAAE,SAAS;GAAS,QAJZ,UACZ,GAAG,WAAW,GAAG,QAAQ,QAAQ,SAAS,IAAI,IAAI,MAAM,IAAI,CAAC,KAAK,IAAI,OACtE;GAEgC;;;;;CAMpC,MAAM,gBACL,YACA,MACA,QAKE;AACF,QAAM,KAAK,cAAc;EACzB,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,MAAM,UAAU,MAAM,KAAK,YAAY;EAEvC,MAAM,aAAa,QAAQC,KAAQ;EACnC,MAAMC,WAA2B;GAChC,QAAQ,cAAc;GACtB;GACA,MAAM;GACN,QAAQ,UAAU,OAAO;GACzB;EAED,MAAM,UAAU,KAAK,OAAO;AAE5B,OAAK,OADe,MAAM,KAAK,YAAY,CAAC,SAAS,EAAE,QAAQ;EAI/D,MAAM,UAAU,GAAG,WAAW,GAAG;EACjC,MAAM,YAAY,MAAM,KAAK,KAAK,KAAK,IAAI,QAAQ;AAEnD,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,4BAA4B,WAAW,GAAG,aAAa;AAIxE,OAAK,QAAS,cAAc,WAAW;AAGvC,MAAI,KAAK,WAAW;GAEnB,MAAM,YAAY,IAAI,UAAU;GAChC,MAAM,OAAO,KAAK,IAAI,QAAQ,IAC5B,KACA,+CACA,KAAK,KAAK,OAAO,IACjB,CACA,SAAS;AAEX,QAAK,MAAM,OAAO,MAAM;IACvB,MAAM,MAAM,IAAI,MAAM,IAAI,IAAc;IACxC,MAAM,QAAQ,IAAI,WAAW,IAAI,MAAqB;AACtD,cAAU,IAAI,KAAK,MAAM;;GAI1B,MAAM,YAAY;IAAE,GAAG;IAAU,KAAK;IAAW;GAEjD,MAAMC,aAAyB;IAC9B,KAAK,KAAK,KAAK;IACf,QAAQ,KAAK,KAAK;IAClB,KAAK,KAAK,KAAK,OAAO;IACtB,OAAO;IACP;IACA,KAAK,CAAC,UAAU;IAChB;GAED,MAAM,QAAQ,MAAM,KAAK,UAAU,eAAe,WAAW;AAC7D,SAAM,KAAK,gBAAgB,MAAM;;AAGlC,SAAO;GACN,KAAK,QAAQ,KAAK,KAAK,IAAI,GAAG,WAAW,GAAG;GAC5C,KAAK,UAAU,UAAU;GACzB,QAAQ;IACP,KAAK,KAAK,KAAK,IAAI,UAAU;IAC7B,KAAK,KAAK,KAAK,OAAO;IACtB;GACD;;;;;CAMF,MAAM,gBACL,YACA,MAC2D;AAC3D,QAAM,KAAK,cAAc;EACzB,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,MAAM,UAAU,MAAM,KAAK,YAAY;AAGvC,MAAI,CADa,MAAM,KAAK,UAAU,YAAY,KAAK,CACxC,QAAO;EAEtB,MAAMC,WAA2B;GAChC,QAAQ,cAAc;GACtB;GACA;GACA;EAED,MAAM,UAAU,KAAK,OAAO;EAC5B,MAAM,cAAc,MAAM,KAAK,YAAY,CAAC,SAAS,EAAE,QAAQ;AAC/D,OAAK,OAAO;AAGZ,MAAI,KAAK,WAAW;GAEnB,MAAM,YAAY,IAAI,UAAU;GAChC,MAAM,OAAO,KAAK,IAAI,QAAQ,IAC5B,KACA,+CACA,KAAK,KAAK,OAAO,IACjB,CACA,SAAS;AAEX,QAAK,MAAM,OAAO,MAAM;IACvB,MAAM,MAAM,IAAI,MAAM,IAAI,IAAc;IACxC,MAAM,QAAQ,IAAI,WAAW,IAAI,MAAqB;AACtD,cAAU,IAAI,KAAK,MAAM;;GAG1B,MAAMD,aAAyB;IAC9B,KAAK,KAAK,KAAK;IACf,QAAQ,KAAK,KAAK;IAClB,KAAK,KAAK,KAAK,OAAO;IACtB,OAAO;IACP;IACA,KAAK,CAAC,SAAS;IACf;GAED,MAAM,QAAQ,MAAM,KAAK,UAAU,eAAe,WAAW;AAC7D,SAAM,KAAK,gBAAgB,MAAM;;AAGlC,SAAO,EACN,QAAQ;GACP,KAAK,YAAY,IAAI,UAAU;GAC/B,KAAK,YAAY,OAAO;GACxB,EACD;;;;;CAMF,MAAM,aACL,YACA,MACA,QAME;AACF,QAAM,KAAK,cAAc;EACzB,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,MAAM,UAAU,MAAM,KAAK,YAAY;EAIvC,MAAM,WADW,MAAM,KAAK,UAAU,YAAY,KAAK,KACzB;EAE9B,MAAM,mBAAmB,UAAU,OAAO;EAC1C,MAAME,KAAoB,WACtB;GACD,QAAQ,cAAc;GACtB;GACA;GACA,QAAQ;GACR,GACC;GACD,QAAQ,cAAc;GACtB;GACA;GACA,QAAQ;GACR;EAEH,MAAM,UAAU,KAAK,OAAO;AAE5B,OAAK,OADe,MAAM,KAAK,YAAY,CAAC,GAAG,EAAE,QAAQ;EAIzD,MAAM,UAAU,GAAG,WAAW,GAAG;EACjC,MAAM,YAAY,MAAM,KAAK,KAAK,KAAK,IAAI,QAAQ;AAEnD,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,yBAAyB,WAAW,GAAG,OAAO;AAI/D,OAAK,QAAS,cAAc,WAAW;AAGvC,MAAI,KAAK,WAAW;GACnB,MAAM,YAAY,IAAI,UAAU;GAChC,MAAM,OAAO,KAAK,IAAI,QAAQ,IAC5B,KACA,+CACA,KAAK,KAAK,OAAO,IACjB,CACA,SAAS;AAEX,QAAK,MAAM,OAAO,MAAM;IACvB,MAAM,MAAM,IAAI,MAAM,IAAI,IAAc;IACxC,MAAM,QAAQ,IAAI,WAAW,IAAI,MAAqB;AACtD,cAAU,IAAI,KAAK,MAAM;;GAG1B,MAAM,YAAY;IAAE,GAAG;IAAI,KAAK;IAAW;GAE3C,MAAMF,aAAyB;IAC9B,KAAK,KAAK,KAAK;IACf,QAAQ,KAAK,KAAK;IAClB,KAAK,KAAK,KAAK,OAAO;IACtB,OAAO;IACP;IACA,KAAK,CAAC,UAAU;IAChB;GAED,MAAM,QAAQ,MAAM,KAAK,UAAU,eAAe,WAAW;AAC7D,SAAM,KAAK,gBAAgB,MAAM;;AAGlC,SAAO;GACN,KAAK,QAAQ,KAAK,KAAK,IAAI,GAAG,WAAW,GAAG;GAC5C,KAAK,UAAU,UAAU;GACzB,QAAQ;IACP,KAAK,KAAK,KAAK,IAAI,UAAU;IAC7B,KAAK,KAAK,KAAK,OAAO;IACtB;GACD,kBAAkB;GAClB;;;;;CAMF,MAAM,eACL,QAcE;AACF,QAAM,KAAK,cAAc;EACzB,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,MAAM,UAAU,MAAM,KAAK,YAAY;EAGvC,MAAMG,MAAuB,EAAE;EAC/B,MAAMC,UAQD,EAAE;AAEP,OAAK,MAAM,SAAS,OACnB,KAAI,MAAM,UAAU,uCAAuC;GAC1D,MAAM,OAAO,MAAM,QAAQN,KAAQ;GACnC,MAAMO,KAAqB;IAC1B,QAAQ,cAAc;IACtB,YAAY,MAAM;IAClB;IACA,QAAQ,UAAU,MAAM,MAAM;IAC9B;AACD,OAAI,KAAK,GAAG;AACZ,WAAQ,KAAK;IACZ,OAAO;IACP,YAAY,MAAM;IAClB;IACA,QAAQ,cAAc;IACtB,CAAC;aACQ,MAAM,UAAU,uCAAuC;AACjE,OAAI,CAAC,MAAM,KACV,OAAM,IAAI,MAAM,uBAAuB;GAExC,MAAMC,KAAqB;IAC1B,QAAQ,cAAc;IACtB,YAAY,MAAM;IAClB,MAAM,MAAM;IACZ,QAAQ,UAAU,MAAM,MAAM;IAC9B;AACD,OAAI,KAAK,GAAG;AACZ,WAAQ,KAAK;IACZ,OAAO;IACP,YAAY,MAAM;IAClB,MAAM,MAAM;IACZ,QAAQ,cAAc;IACtB,CAAC;aACQ,MAAM,UAAU,uCAAuC;AACjE,OAAI,CAAC,MAAM,KACV,OAAM,IAAI,MAAM,uBAAuB;GAExC,MAAMC,KAAqB;IAC1B,QAAQ,cAAc;IACtB,YAAY,MAAM;IAClB,MAAM,MAAM;IACZ;AACD,OAAI,KAAK,GAAG;AACZ,WAAQ,KAAK;IACZ,OAAO;IACP,YAAY,MAAM;IAClB,MAAM,MAAM;IACZ,QAAQ,cAAc;IACtB,CAAC;QAEF,OAAM,IAAI,MAAM,uBAAuB,MAAM,QAAQ;EAIvD,MAAM,UAAU,KAAK,OAAO;AAE5B,OAAK,OADe,MAAM,KAAK,YAAY,KAAK,QAAQ;AAIxD,OAAK,MAAM,MAAM,IAChB,KAAI,GAAG,WAAW,cAAc,OAC/B,MAAK,QAAS,cAAc,GAAG,WAAW;EAK5C,MAAMC,eAKD,EAAE;EACP,MAAMC,cAA2D,EAAE;AAEnE,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;GACxC,MAAM,SAAS,QAAQ;GACvB,MAAM,KAAK,IAAI;AAEf,OAAI,OAAO,WAAW,cAAc,QAAQ;AAC3C,iBAAa,KAAK,EACjB,OAAO,OAAO,OACd,CAAC;AACF,gBAAY,KAAK,GAAG;UACd;IAEN,MAAM,UAAU,GAAG,OAAO,WAAW,GAAG,OAAO;IAC/C,MAAM,YAAY,MAAM,KAAK,KAAK,KAAK,IAAI,QAAQ;AACnD,iBAAa,KAAK;KACjB,OAAO,OAAO;KACd,KAAK,QAAQ,KAAK,KAAK,IAAI,GAAG,OAAO,WAAW,GAAG,OAAO;KAC1D,KAAK,WAAW,UAAU;KAC1B,kBAAkB;KAClB,CAAC;AAEF,gBAAY,KAAK;KAAE,GAAG;KAAI,KAAK;KAAW,CAAC;;;AAK7C,MAAI,KAAK,WAAW;GACnB,MAAM,YAAY,IAAI,UAAU;GAChC,MAAM,OAAO,KAAK,IAAI,QAAQ,IAC5B,KACA,+CACA,KAAK,KAAK,OAAO,IACjB,CACA,SAAS;AAEX,QAAK,MAAM,OAAO,MAAM;IACvB,MAAM,MAAM,IAAI,MAAM,IAAI,IAAc;IACxC,MAAM,QAAQ,IAAI,WAAW,IAAI,MAAqB;AACtD,cAAU,IAAI,KAAK,MAAM;;GAG1B,MAAMT,aAAyB;IAC9B,KAAK,KAAK,KAAK;IACf,QAAQ,KAAK,KAAK;IAClB,KAAK,KAAK,KAAK,OAAO;IACtB,OAAO;IACP;IACA,KAAK;IACL;GAED,MAAM,QAAQ,MAAM,KAAK,UAAU,eAAe,WAAW;AAC7D,SAAM,KAAK,gBAAgB,MAAM;;AAGlC,SAAO;GACN,QAAQ;IACP,KAAK,KAAK,KAAK,IAAI,UAAU;IAC7B,KAAK,KAAK,KAAK,OAAO;IACtB;GACD,SAAS;GACT;;;;;CAMF,MAAM,mBAIH;EACF,MAAM,OAAO,MAAM,KAAK,SAAS;AACjC,SAAO;GACN,KAAK,KAAK;GACV,MAAM,KAAK,IAAI,UAAU;GACzB,KAAK,KAAK,OAAO;GACjB;;;;;CAMF,MAAM,gBAAqC;EAE1C,MAAM,OAAO,OADG,MAAM,KAAK,YAAY,EACZ,SAAS;AAEpC,MAAI,CAAC,KACJ,OAAM,IAAI,MAAM,2BAA2B;EAI5C,MAAM,OAAO,KAAK,IAAI,QAAQ,IAC5B,KAAK,gCAAgC,CACrC,SAAS;EAGX,MAAM,SAAS,IAAI,UAAU;AAC7B,OAAK,MAAM,OAAO,MAAM;GACvB,MAAM,MAAM,IAAI,MAAM,IAAI,IAAc;GACxC,MAAM,QAAQ,IAAI,WAAW,IAAI,MAAqB;AACtD,UAAO,IAAI,KAAK,MAAM;;AAIvB,SAAO,gBAAgB,MAAM,OAAO;;;;;;CAOrC,MAAM,aAAa,MAAqC;EACvD,MAAM,UAAU,MAAM,KAAK,YAAY;EACvC,MAAM,OAAO,MAAM,QAAQ,SAAS;AAEpC,MAAI,CAAC,KACJ,OAAM,IAAI,MAAM,2BAA2B;EAI5C,MAAM,SAAS,IAAI,UAAU;AAC7B,OAAK,MAAM,UAAU,MAAM;GAC1B,MAAM,MAAM,IAAI,MAAM,OAAO;GAC7B,MAAM,QAAQ,MAAM,QAAQ,SAAS,IAAI;AACzC,OAAI,MACH,QAAO,IAAI,KAAK,MAAM;;AAKxB,SAAO,gBAAgB,MAAM,OAAO;;;;;;;;CASrC,MAAM,kBACL,YACA,MACsB;EACtB,MAAM,UAAU,MAAM,KAAK,YAAY;EACvC,MAAM,OAAO,MAAM,QAAQ,SAAS;AAEpC,MAAI,CAAC,KACJ,OAAM,IAAI,MAAM,2BAA2B;EAK5C,MAAMU,YAA0B,EAAE;AAClC,aAAW,MAAM,SAAS,WAAW,SAAS,MAAM,CACnD;GAAE;GAAY;GAAM,CACpB,CAAC,CACD,WAAU,KAAK,MAAM;EAItB,MAAM,cAAc,UAAU,QAAQ,KAAK,UAAU,MAAM,MAAM,QAAQ,EAAE;EAC3E,MAAM,SAAS,IAAI,WAAW,YAAY;EAC1C,IAAI,SAAS;AACb,OAAK,MAAM,SAAS,WAAW;AAC9B,UAAO,IAAI,OAAO,OAAO;AACzB,aAAU,MAAM;;AAGjB,SAAO;;;;;;;CAQR,MAAM,cAAc,UAIjB;AACF,QAAM,KAAK,0BAA0B;EAGrC,MAAM,WAAW,MAAM,KAAK,QAAS,WAAW;EAChD,MAAM,eAAe,MAAM,KAAK,QAAS,SAAS;AAElD,MAAI,YAAY,aAEf,OAAM,IAAI,MACT,qEACA;AAIF,MAAI,cAAc;AACjB,SAAM,KAAK,QAAS,SAAS;AAC7B,QAAK,OAAO;AACZ,QAAK,kBAAkB;;EAKxB,MAAM,EAAE,MAAM,SAAS,WAAW,MAAM,gBAAgB,SAAS;EAGjE,MAAM,YAAYZ,KAAQ;AAC1B,QAAM,KAAK,QAAS,QAAQ,QAAQ,UAAU;AAG9C,OAAK,UAAU,MAAM,iBAAiB,OAAO,KAAK,IAAI,YAAY;AAClE,OAAK,OAAO,MAAM,KAAK,KAAK,KAAK,SAAU,QAAQ;AAGnD,QAAM,KAAK,QAAS,WAAW,SAAS,KAAK,KAAK,OAAO,IAAI;AAG7D,MAAI,KAAK,KAAK,QAAQ,KAAK,IAAI,KAAK;AAEnC,SAAM,KAAK,QAAS,SAAS;AAC7B,SAAM,IAAI,MACT,uCAAuC,KAAK,KAAK,IAAI,iBAAiB,KAAK,IAAI,MAC/E;;AAGF,OAAK,kBAAkB;EAGvB,MAAM,kCAAkB,IAAI,KAAa;AACzC,aAAW,MAAM,UAAU,KAAK,KAAK,aAAa,EAAE;AACnD,OAAI,CAAC,gBAAgB,IAAI,OAAO,WAAW,EAAE;AAC5C,oBAAgB,IAAI,OAAO,WAAW;AACtC,SAAK,QAAS,cAAc,OAAO,WAAW;;GAE/C,MAAM,WAAW,gBAAgB,OAAO,OAAO;AAC/C,OAAI,SAAS,SAAS,GAAG;IACxB,MAAM,MAAM,QAAQ,KAAK,KAAK,IAAI,GAAG,OAAO,WAAW,GAAG,OAAO;AACjE,SAAK,QAAS,eAAe,KAAK,SAAS;;;AAI7C,SAAO;GACN,KAAK,KAAK,KAAK;GACf,KAAK,KAAK,KAAK,OAAO;GACtB,KAAK,QAAQ,UAAU;GACvB;;;;;CAMF,MAAM,cAAc,OAAmB,UAAoC;AAC1E,MAAI,CAAC,KAAK,UACT,OAAM,IAAI,MAAM,8BAA8B;EAI/C,MAAM,gBAAgB,KAAK,OAAO;AAClC,MAAI,MAAM,SAAS,cAClB,OAAM,IAAI,MACT,mBAAmB,MAAM,OAAO,cAAc,cAAc,GAC5D;EAGF,MAAM,UAAU,MAAM,KAAK,UAAU,QAAQ,OAAO,SAAS;AAI7D,GADgB,MAAM,KAAK,YAAY,EAC/B,kBAAkB,QAAQ,IAAI,OAAO,MAAM,QAAQ,SAAS;AAEpE,SAAO;;;;;CAMR,MAAM,WAAW,QAA8C;AAC9D,MAAI,CAAC,KAAK,UACT,OAAM,IAAI,MAAM,8BAA8B;AAE/C,SAAO,KAAK,UAAU,QAAQ,OAAO;;;;;CAMtC,AAAQ,YAAY,QAAgB,MAA0B;EAC7D,MAAM,cAAca,SAAW,OAAc;EAC7C,MAAM,YAAYA,SAAW,KAAY;EAEzC,MAAM,QAAQ,IAAI,WAAW,YAAY,SAAS,UAAU,OAAO;AACnE,QAAM,IAAI,aAAa,EAAE;AACzB,QAAM,IAAI,WAAW,YAAY,OAAO;AAExC,SAAO;;;;;CAMR,AAAQ,kBAAkB,OAAmC;AAE5D,SAAO,KAAK,YADG;GAAE,IAAI;GAAG,GAAG;GAAW,EACN,MAAM,MAAM;;;;;CAM7C,AAAQ,oBAAoB,OAAqC;AAEhE,SAAO,KAAK,YADG;GAAE,IAAI;GAAG,GAAG;GAAa,EACR,MAAM,MAAM;;;;;CAM7C,AAAQ,iBAAiB,OAA6B;AACrD,MAAI,MAAM,SAAS,WAClB,QAAO,KAAK,oBAAoB,MAAM;AAEvC,SAAO,KAAK,kBAAkB,MAAM;;;;;CAMrC,AAAQ,iBAAiB,OAAe,SAA6B;EACpE,MAAM,SAAS,EAAE,IAAI,IAAI;EACzB,MAAM,OAAO;GAAE;GAAO;GAAS;AAC/B,SAAO,KAAK,YAAY,QAAQ,KAAK;;;;;CAMtC,MAAc,iBAAiB,IAAe,QAA+B;AAC5E,MAAI,CAAC,KAAK,UACT,OAAM,IAAI,MAAM,4BAA4B;AAM7C,MAAI,SAHc,KAAK,UAAU,cAAc,EAGvB;GACvB,MAAM,QAAQ,KAAK,iBAClB,gBACA,0BACA;AACD,MAAG,KAAK,MAAM;AACd,MAAG,MAAM,MAAM,eAAe;AAC9B;;EAID,MAAM,SAAS,MAAM,KAAK,UAAU,eAAe,QAAQ,IAAK;AAEhE,OAAK,MAAM,SAAS,QAAQ;GAC3B,MAAM,QAAQ,KAAK,iBAAiB,MAAM;AAC1C,MAAG,KAAK,MAAM;;AAIf,MAAI,OAAO,SAAS,GAAG;GACtB,MAAM,YAAY,OAAO,OAAO,SAAS;AACzC,OAAI,WAAW;IACd,MAAM,aAAa,GAAG,uBAAuB;AAC7C,eAAW,SAAS,UAAU;AAC9B,OAAG,oBAAoB,WAAW;;;;;;;CAQrC,MAAc,gBAAgB,OAAgC;EAC7D,MAAM,QAAQ,KAAK,iBAAiB,MAAM;AAE1C,OAAK,MAAM,MAAM,KAAK,IAAI,eAAe,CACxC,KAAI;AACH,MAAG,KAAK,MAAM;GAGd,MAAM,aAAa,GAAG,uBAAuB;AAC7C,cAAW,SAAS,MAAM;AAC1B,MAAG,oBAAoB,WAAW;WAC1B,GAAG;AAEX,WAAQ,MAAM,oCAAoC,EAAE;;;;;;CAQvD,MAAM,sBAAsB,SAAqC;AAChE,QAAM,KAAK,0BAA0B;EAGrC,MAAM,cADM,IAAI,IAAI,QAAQ,IAAI,CACR,aAAa,IAAI,SAAS;EAClD,MAAM,SAAS,cAAc,SAAS,aAAa,GAAG,GAAG;EAGzD,MAAM,OAAO,IAAI,eAAe;EAChC,MAAM,SAAS,KAAK;EACpB,MAAM,SAAS,KAAK;AAGpB,OAAK,IAAI,gBAAgB,OAAO;AAGhC,SAAO,oBAAoB;GAC1B,QAAQ,UAAU;GAClB,aAAa,KAAK,KAAK;GACvB,CAAC;AAGF,MAAI,WAAW,KACd,OAAM,KAAK,iBAAiB,QAAQ,OAAO;AAG5C,SAAO,IAAI,SAAS,MAAM;GACzB,QAAQ;GACR,WAAW;GACX,CAAC;;;;;CAMH,AAAS,iBACR,KACA,UACO;;;;CAOR,AAAS,eACR,KACA,OACA,SACA,WACO;;;;CAOR,AAAS,eAAe,KAAgB,OAAoB;AAC3D,UAAQ,MAAM,oBAAoB,MAAM;;;;;CAMzC,MAAM,oBAAyD;AAG9D,SAAO,EAAE,aADW,OADJ,MAAM,KAAK,YAAY,EACL,gBAAgB,EAC5B;;;;;CAMvB,MAAM,kBAAkB,aAAuC;AAE9D,SADgB,MAAM,KAAK,YAAY,EACzB,eAAe,YAAY;;;;;CAM1C,MAAM,cAAiD;AAEtD,SAAO,EAAE,QADO,MAAM,KAAK,YAAY,EACf,UAAU,EAAE;;;;;CAMrC,MAAM,eAAe,OAA8B;AAElD,GADgB,MAAM,KAAK,YAAY,EAC/B,SAAS,MAAM;;;;;CAMxB,MAAM,eAAiC;AAEtC,UADgB,MAAM,KAAK,YAAY,EACxB,WAAW;;;;;CAM3B,MAAM,qBAAoC;AAEzC,SADgB,MAAM,KAAK,YAAY,EACzB,UAAU,KAAK;;;;;CAM9B,MAAM,uBAAsC;AAE3C,SADgB,MAAM,KAAK,YAAY,EACzB,UAAU,MAAM;;;;;CAU/B,MAAM,iBAAkC;AAEvC,UADgB,MAAM,KAAK,YAAY,EACxB,aAAa;;;;;CAM7B,MAAM,kBAAmC;EACxC,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,IAAI,QAAQ;AACZ,aAAW,MAAM,WAAW,KAAK,aAAa,CAC7C;AAED,SAAO;;;;;CAMR,MAAM,wBAAyC;AAE9C,UADgB,MAAM,KAAK,YAAY,EACxB,oBAAoB;;;;;CAMpC,MAAM,wBAAyC;AAE9C,UADgB,MAAM,KAAK,YAAY,EACxB,oBAAoB;;;;;CAMpC,MAAM,oBACL,QAAgB,KAChB,QAIE;AAEF,UADgB,MAAM,KAAK,YAAY,EACxB,iBAAiB,OAAO,OAAO;;;;;;;CAQ/C,MAAM,oBAGH;EACF,MAAM,UAAU,MAAM,KAAK,YAAY;AAIvC,MADiB,MAAM,QAAQ,WAAW,CAEzC,OAAM,IAAI,MACT,gFACA;EAIF,MAAM,gBAAgB,MAAM,QAAQ,aAAa;EACjD,MAAM,eAAe,QAAQ,oBAAoB;AAGjD,QAAM,QAAQ,SAAS;AAGvB,UAAQ,mBAAmB;AAG3B,OAAK,OAAO;AACZ,OAAK,kBAAkB;AAEvB,SAAO;GAAE;GAAe;GAAc;;;;;CAMvC,MAAM,qBAAqB,QAA0C;AACpE,QAAM,KAAK,0BAA0B;EAErC,MAAM,wBAAO,IAAI,MAAM,EAAC,aAAa;EAWrC,MAAM,MARS,KAAK,IAAI,QAAQ,IAC9B,KACA;;qBAGA,IAAI,WAAW,EAAE,CACjB,CACA,KAAK,CACY;EAGnB,MAAM,SAAS;GAAE,IAAI;GAAG,GAAG;GAAa;EACxC,MAAM,OAAO;GACZ;GACA,KAAK,KAAK,IAAI;GACd;GACA;GACA;EAED,MAAM,cAAcA,SAAW,OAAO;EACtC,MAAM,YAAYA,SAAW,KAAK;EAClC,MAAM,QAAQ,IAAI,WAAW,YAAY,SAAS,UAAU,OAAO;AACnE,QAAM,IAAI,aAAa,EAAE;AACzB,QAAM,IAAI,WAAW,YAAY,OAAO;AAGxC,OAAK,MAAM,MAAM,KAAK,IAAI,eAAe,CACxC,KAAI;AACH,MAAG,KAAK,MAAM;WACN,GAAG;AACX,WAAQ,MAAM,sCAAsC,EAAE;;AAIxD,SAAO,EAAE,KAAK;;;;;CAUf,MAAM,iBAAwC;AAC7C,OAAK,IAAI,QAAQ,IAAI,KAAK,WAAW,CAAC,SAAS;AAC/C,SAAO,EAAE,IAAI,MAAM;;;;;CAMpB,MAAM,uBAGH;EACF,MAAM,UAAU,KAAK,IAAI,eAAe;AACxC,QAAM,KAAK,0BAA0B;EAErC,MAAM,MAAM,OADI,MAAM,KAAK,YAAY,EACb,QAAQ;AAClC,SAAO;GACN,aAAa,QAAQ;GACrB,WAAW,OAAO;GAClB;;;CASF,MAAM,gBACL,MACA,MACgB;AAEhB,SADgB,MAAM,KAAK,iBAAiB,EAC9B,aAAa,MAAM,KAAK;;;CAIvC,MAAM,eACL,MACmE;AAEnE,UADgB,MAAM,KAAK,iBAAiB,EAC7B,YAAY,KAAK;;;CAIjC,MAAM,kBAAkB,MAA6B;AAEpD,SADgB,MAAM,KAAK,iBAAiB,EAC9B,eAAe,KAAK;;;CAInC,MAAM,cACL,MACgB;AAEhB,SADgB,MAAM,KAAK,iBAAiB,EAC9B,WAAW,KAAK;;;CAI/B,MAAM,oBACL,aACgE;AAEhE,UADgB,MAAM,KAAK,iBAAiB,EAC7B,iBAAiB,YAAY;;;CAI7C,MAAM,qBACL,cACgE;AAEhE,UADgB,MAAM,KAAK,iBAAiB,EAC7B,kBAAkB,aAAa;;;CAI/C,MAAM,eAAe,aAAoC;AAExD,SADgB,MAAM,KAAK,iBAAiB,EAC9B,YAAY,YAAY;;;CAIvC,MAAM,mBAAmB,KAA4B;AAEpD,SADgB,MAAM,KAAK,iBAAiB,EAC9B,gBAAgB,IAAI;;;CAInC,MAAM,cACL,UACA,UACgB;AAEhB,SADgB,MAAM,KAAK,iBAAiB,EAC9B,WAAW,UAAU,SAAS;;;CAI7C,MAAM,aACL,UACqE;AAErE,UADgB,MAAM,KAAK,iBAAiB,EAC7B,UAAU,SAAS;;;CAInC,MAAM,WACL,YACA,MACgB;AAEhB,SADgB,MAAM,KAAK,iBAAiB,EAC9B,QAAQ,YAAY,KAAK;;;CAIxC,MAAM,UACL,YAC8D;AAE9D,UADgB,MAAM,KAAK,iBAAiB,EAC7B,OAAO,WAAW;;;CAIlC,MAAM,aAAa,YAAmC;AAErD,SADgB,MAAM,KAAK,iBAAiB,EAC9B,UAAU,WAAW;;;CAIpC,MAAM,qBAAqB,OAAiC;AAE3D,UADgB,MAAM,KAAK,iBAAiB,EAC7B,kBAAkB,MAAM;;;CAQxC,MAAM,eACL,cACA,WACA,SACA,MACgB;AAEhB,GADgB,MAAM,KAAK,YAAY,EAC/B,YAAY,cAAc,WAAW,SAAS,KAAK;;;CAI5D,MAAM,cAAc,cAOV;AAET,UADgB,MAAM,KAAK,YAAY,EACxB,WAAW,aAAa;;;CAIxC,MAAM,kBAKF;AAEH,UADgB,MAAM,KAAK,YAAY,EACxB,cAAc;;;CAI9B,MAAM,iBAAiB,cAAwC;AAE9D,UADgB,MAAM,KAAK,YAAY,EACxB,cAAc,aAAa;;;CAI3C,MAAM,wBACL,cACA,SACgB;AAEhB,GADgB,MAAM,KAAK,YAAY,EAC/B,qBAAqB,cAAc,QAAQ;;;CAIpD,MAAM,iBAAmC;AAExC,UADgB,MAAM,KAAK,YAAY,EACxB,aAAa;;;CAI7B,MAAM,oBACL,OACA,WACA,WACA,MACgB;AAEhB,GADgB,MAAM,KAAK,YAAY,EAC/B,iBAAiB,OAAO,WAAW,WAAW,KAAK;;;CAI5D,MAAM,uBACL,OAC6D;AAE7D,UADgB,MAAM,KAAK,YAAY,EACxB,oBAAoB,MAAM;;;CAI1C,MAAM,yBAAyB,WAAkC;AAEhE,GADqB,MAAM,KAAK,iBAAiB,EACpC,sBAAsB,UAAU;;;CAI9C,MAAM,4BAA4B,WAAqC;AAEtE,UADqB,MAAM,KAAK,iBAAiB,EAC7B,yBAAyB,UAAU;;;;;;CAOxD,MAAe,MAAM,SAAqC;AAGzD,MADY,IAAI,IAAI,QAAQ,IAAI,CACxB,aAAa,wCACpB,QAAO,KAAK,sBAAsB,QAAQ;AAI3C,SAAO,IAAI,SAAS,sBAAsB,EAAE,QAAQ,KAAK,CAAC;;;;;;;AAQ5D,SAAS,gBAAgB,KAAuB;AAC/C,KAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;CAG9C,MAAM,MAAM,MAAM,IAAI;AACtB,KAAI,IACH,QAAO,EAAE,OAAO,IAAI,UAAU,EAAE;AAIjC,KAAI,eAAe,YAAY;EAC9B,IAAI,SAAS;AACb,OAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC/B,WAAU,OAAO,aAAa,IAAI,GAAI;AAEvC,SAAO,EAAE,QAAQ,KAAK,OAAO,EAAE;;AAGhC,KAAI,MAAM,QAAQ,IAAI,CACrB,QAAO,IAAI,IAAI,gBAAgB;AAGhC,KAAI,OAAO,QAAQ,UAAU;EAC5B,MAAMC,SAAkC,EAAE;AAC1C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,CAC7C,QAAO,OAAO,gBAAgB,MAAM;AAErC,SAAO;;AAGR,QAAO;;;;;;AAOR,SAAS,gBAAgB,KAAwB;CAChD,MAAMC,OAAiB,EAAE;CAEzB,SAAS,KAAK,OAAsB;AACnC,MAAI,UAAU,QAAQ,UAAU,OAAW;AAG3C,MAAI,UAAU,MAAM,EAAE;AACrB,QAAK,KAAK,MAAM,IAAI,UAAU,CAAC;AAC/B;;AAGD,MAAI,MAAM,QAAQ,MAAM,CACvB,MAAK,MAAM,QAAQ,MAClB,MAAK,KAAK;WAED,OAAO,UAAU,SAE3B,MAAK,MAAM,OAAO,OAAO,KAAK,MAAiC,CAC9D,MAAM,MAAkC,KAAK;;AAKhD,MAAK,IAAI;AACT,QAAO;;;;;AChlDR,MAAM,6BAA6B;;;;AAKnC,IAAIC,gBAAyC;AAC7C,IAAIC,mBAAkC;;;;;AAMtC,eAAsB,kBACrB,YAC4B;AAC5B,KAAI,iBAAiB,qBAAqB,WACzC,QAAO;AAER,iBAAgB,MAAM,iBAAiB,OAAO,WAAW;AACzD,oBAAmB;AACnB,QAAO;;AAsBR,SAAS,aAAa,MAAuC;AAC5D,QAAO,OAAO,KAAK,KAAK,UAAU,KAAK,CAAC,CAAC,SAAS,YAAY;;AAG/D,SAAS,gBACR,KACa;CACb,MAAMC,SAAqB,EAAE;AAC7B,MAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,IAAI,CAC3C,KAAI,QAAQ,OACX,QAAO,OAAkB;AAG3B,QAAO;;;;;;AAOR,eAAsB,iBACrB,QACkB;CAClB,MAAM,EAAE,KAAK,KAAK,YAAY;CAC9B,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;CACzC,MAAM,MAAM,MAAM;CAClB,MAAM,MAAM,OAAO,OAAO;CAC1B,MAAM,MAAM,UAAU,IAAI,MAAM;CAEhC,MAAM,SAAS;EACd,KAAK;EACL,KAAK,QAAQ;EACb;CAED,MAAM,UAAU,gBAAgB;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA,CAAC;CAEF,MAAM,YAAY,GAAG,aAAa,OAAO,CAAC,GAAG,aAAa,QAAmC;CAC7F,MAAM,SAAS,OAAO,KAAK,WAAW,OAAO;AAG7C,QAAO,GAAG,UAAU,GAFR,OAAO,KAAK,MAAM,QAAQ,KAAK,OAAO,CAAC,CAExB,SAAS,YAAY;;;;;;;AAQjD,eAAsB,iBACrB,OACA,YACA,kBACA,gBAC6B;CAC7B,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,KAAI,MAAM,WAAW,EACpB,OAAM,IAAI,MAAM,qBAAqB;CAGtC,MAAM,YAAY,MAAM;CACxB,MAAM,aAAa,MAAM;CACzB,MAAM,eAAe,MAAM;CAG3B,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,WAAW,YAAY,CAAC,UAAU,CAAC;AACzE,KAAI,OAAO,QAAQ,SAClB,OAAM,IAAI,MAAM,0BAA0B,OAAO,MAAM;CAIxD,MAAMC,UAA6B,KAAK,MACvC,OAAO,KAAK,YAAY,YAAY,CAAC,UAAU,CAC/C;CAGD,MAAMC,QAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AACzC,KAAI,QAAQ,OAAO,QAAQ,MAAMA,MAChC,OAAM,IAAI,MAAM,gBAAgB;AAIjC,KAAI,QAAQ,QAAQ,iBACnB,OAAM,IAAI,MAAM,8BAA8B,mBAAmB;AAIlE,KAAI,QAAQ,QAAQ,eACnB,OAAM,IAAI,MAAM,4BAA4B,iBAAiB;CAI9D,MAAM,UAAU,MAAM,kBAAkB,WAAW;CAEnD,MAAM,WAAW,IAAI,WACpB,OAAO,KAAK,GAAG,UAAU,GAAG,cAAc,OAAO,CACjD;CACD,MAAM,WAAW,IAAI,WAAW,OAAO,KAAK,cAAc,YAAY,CAAC;AAMvE,KAAI,CAJY,MAAM,gBAAgB,QAAQ,KAAK,EAAE,UAAU,UAAU,EACxE,mBAAmB,MACnB,CAAC,CAGD,OAAM,IAAI,MAAM,oBAAoB;AAGrC,QAAO;;;;;;;;;AC1JR,IAAa,oBAAb,cAAuC,MAAM;CAC5C,YAAY,UAAU,qBAAqB;AAC1C,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAKd,MAAM,wBAAwB;AAC9B,MAAM,yBAAyB;;;;AAK/B,SAAS,gBAAgB,QAA4B;AACpD,QAAO,IAAI,aAAa,CAAC,OAAO,OAAO;;;;;AAMxC,eAAsB,kBACrB,WACA,SACA,YACkB;CAClB,MAAM,SAAS,gBAAgB,UAAU;AAEzC,QAAO,IAAI,QAAQ,EAAE,OAAO,sBAAsB,CAAC,CACjD,mBAAmB;EAAE,KAAK;EAAS,KAAK;EAAU,CAAC,CACnD,aAAa,CACb,YAAY,WAAW,CACvB,WAAW,QAAQ,CACnB,kBAAkB,sBAAsB,CACxC,KAAK,OAAO;;;;;AAMf,eAAsB,mBACrB,WACA,SACA,YACkB;CAClB,MAAM,SAAS,gBAAgB,UAAU;CACzC,MAAM,MAAM,OAAO,YAAY;AAE/B,QAAO,IAAI,QAAQ,EAAE,OAAO,uBAAuB,CAAC,CAClD,mBAAmB;EAAE,KAAK;EAAS,KAAK;EAAe,CAAC,CACxD,aAAa,CACb,YAAY,WAAW,CACvB,WAAW,QAAQ,CACnB,OAAO,IAAI,CACX,kBAAkB,uBAAuB,CACzC,KAAK,OAAO;;;;;;AAOf,eAAsB,kBACrB,OACA,WACA,YACsB;CACtB,MAAM,SAAS,gBAAgB,UAAU;CAEzC,IAAIC;CACJ,IAAIC;AAEJ,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,OAAO,QAAQ,EAC7C,UAAU,YACV,CAAC;AACF,YAAU,OAAO;AACjB,oBAAkB,OAAO;UACjB,KAAK;AACb,MAAI,eAAe,OAAO,WACzB,OAAM,IAAI,mBAAmB;AAE9B,QAAM;;AAIP,KAAI,gBAAgB,QAAQ,SAC3B,OAAM,IAAI,MAAM,qBAAqB;AAItC,KAAI,QAAQ,UAAU,qBACrB,OAAM,IAAI,MAAM,gBAAgB;AAGjC,QAAO;;;;;;AAOR,eAAsB,mBACrB,OACA,WACA,YACsB;CACtB,MAAM,SAAS,gBAAgB,UAAU;CAEzC,IAAID;CACJ,IAAIC;AAEJ,KAAI;EACH,MAAM,SAAS,MAAM,UAAU,OAAO,QAAQ,EAC7C,UAAU,YACV,CAAC;AACF,YAAU,OAAO;AACjB,oBAAkB,OAAO;UACjB,KAAK;AACb,MAAI,eAAe,OAAO,WACzB,OAAM,IAAI,mBAAmB;AAE9B,QAAM;;AAIP,KAAI,gBAAgB,QAAQ,cAC3B,OAAM,IAAI,MAAM,qBAAqB;AAItC,KAAI,QAAQ,UAAU,sBACrB,OAAM,IAAI,MAAM,gBAAgB;AAIjC,KAAI,CAAC,QAAQ,IACZ,OAAM,IAAI,MAAM,mBAAmB;AAGpC,QAAO;;;;;;;;;;;;;;;;ACtFR,MAAM,eAAe,MAAU;;;;AAK/B,SAAS,gBAAwB;CAChC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAE7B,QAAO,KAAK,OAAO,aAAa,GAAG,MAAM,CAAC,CACxC,QAAQ,OAAO,IAAI,CACnB,QAAQ,OAAO,IAAI,CACnB,QAAQ,MAAM,GAAG;;;;;;;;AA6BpB,eAAsB,wBACrB,WACA,aACA,KACA,MACmC;CACnC,MAAM,QAAQ,eAAe;CAC7B,MAAM,YAAY,KAAK,KAAK,GAAG;CAI/B,MAAM,UAAU,MAAM,4BAA4B;EACjD,QAAQ;EACR,MAAM;EACN,UAAU;EACV,iBAAiB,QAAQ;EAEzB,wBAAwB;GACvB,aAAa;GACb,kBAAkB;GAClB;EAED,iBAAiB;EACjB,CAAC;AAGF,OAAM,UAAU,oBAAoB,OAAO,QAAQ,WAAW,WAAW,KAAK;AAI9E,QAAO;EACN;EACA,KAJW,WAAW,YAAY,0BAA0B;EAK5D;EACA;;;;;;;;AASF,eAAsB,uBACrB,WACA,aACA,KACA,OACyD;CAGzD,MAAM,UAAU,MAAM,UAAU,uBAAuB,MAAM;AAC7D,KAAI,CAAC,QACJ,QAAO;AAKR,OAAM,UAAU,oBACf,OACA,QAAQ,WACR,KAAK,KAAK,GAAG,cACb,QAAQ,QAAQ,OAChB;AAsBD,QAAO;EACN,GAjBe,MAAM,4BAA4B;GACjD,QAAQ;GACR,MAAM;GACN,UAAU;GACV,iBAAiB;GACjB,wBAAwB;IACvB,aAAa;IACb,kBAAkB;IAClB;GACD,iBAAiB;GACjB,qBAbwB,MAAM,UAAU,iBAAiB,EAapB,KAAK,QAAQ,EACjD,IAAI,GAAG,cACP,EAAE;GACH,CAAC;EAKD,WAAW,QAAQ;EACnB;;;;;;;;AASF,eAAsB,4BACrB,WACA,aACA,OACA,UACiE;CAEjE,MAAM,YAAY,MAAM,UAAU,uBAAuB,MAAM;AAC/D,KAAI,CAAC,UACJ,QAAO;EAAE,SAAS;EAAO,OAAO;EAA4B;AAG7D,KAAI;EAEH,MAAM,eAAe,MAAM,2BAA2B;GACrD;GACA,mBAAmB,UAAU;GAC7B,gBAAgB,WAAW;GAC3B,cAAc;GACd,CAAC;AAEF,MAAI,CAAC,aAAa,YAAY,CAAC,aAAa,iBAC3C,QAAO;GAAE,SAAS;GAAO,OAAO;GAAuB;EAGxD,MAAM,EAAE,eAAe,aAAa;AAGpC,QAAM,UAAU,eACf,WAAW,IACX,WAAW,WACX,WAAW,SACX,UAAU,QAAQ,OAClB;AAED,SAAO,EAAE,SAAS,MAAM;UAChB,KAAK;AACb,UAAQ,MAAM,+BAA+B,IAAI;AACjD,SAAO;GACN,SAAS;GACT,OAAO,eAAe,QAAQ,IAAI,UAAU;GAC5C;;;;;;;AAQH,eAAsB,yBACrB,WACA,aACwD;CAExD,MAAM,WAAW,MAAM,UAAU,iBAAiB;AAClD,KAAI,SAAS,WAAW,EACvB,QAAO;CAIR,MAAM,UAAU,MAAM,8BAA8B;EACnD,MAAM;EACN,kBAAkB;EAClB,kBAAkB,SAAS,KAAK,QAAQ;GACvC,IAAI,GAAG;GAEP,YAAY;IAAC;IAAY;IAAU;IAAO;IAAO;IAAM;GACvD,EAAE;EACH,CAAC;AAGF,OAAM,UAAU,yBAAyB,QAAQ,UAAU;AAE3D,QAAO;;;;;;AAOR,eAAsB,4BACrB,WACA,aACA,UACA,WACiE;AACjE,KAAI;AAGH,MAAI,CADmB,MAAM,UAAU,4BAA4B,UAAU,CAE5E,QAAO;GAAE,SAAS;GAAO,OAAO;GAAgC;EAIjE,MAAM,UAAU,MAAM,UAAU,cAAc,SAAS,GAAG;AAC1D,MAAI,CAAC,QACJ,QAAO;GAAE,SAAS;GAAO,OAAO;GAAsB;EAIvD,MAAM,eAAe,MAAM,6BAA6B;GACvD;GACA,mBAAmB;GACnB,gBAAgB,WAAW;GAC3B,cAAc;GACd,YAAY;IACX,IAAI,QAAQ;IACZ,WAAW,IAAI,WAAW,QAAQ,UAAU;IAC5C,SAAS,QAAQ;IACjB;GACD,CAAC;AAEF,MAAI,CAAC,aAAa,SACjB,QAAO;GAAE,SAAS;GAAO,OAAO;GAAuB;AAIxD,QAAM,UAAU,wBACf,SAAS,IACT,aAAa,mBAAmB,WAChC;AAED,SAAO,EAAE,SAAS,MAAM;UAChB,KAAK;AACb,UAAQ,MAAM,iCAAiC,IAAI;AACnD,SAAO;GACN,SAAS;GACT,OAAO,eAAe,QAAQ,IAAI,UAAU;GAC5C;;;;;;AAOH,eAAsB,aACrB,WACyB;AAEzB,SADiB,MAAM,UAAU,iBAAiB,EAClC,KAAK,QAAQ;EAC5B,IAAI,GAAG;EACP,MAAM,GAAG;EACT,WAAW,GAAG;EACd,YAAY,GAAG;EACf,EAAE;;;;;AAMJ,eAAsB,cACrB,WACA,cACmB;AACnB,QAAO,UAAU,iBAAiB,aAAa;;;;;;;;;;;;;;;;;;;AChUhD,IAAM,sBAAN,MAAkD;CACjD,YAAY,AAAQC,WAAoD;EAApD;;CAEpB,MAAM,aAAa,MAAc,MAAmC;AACnE,QAAM,KAAK,UAAU,gBAAgB,MAAM,KAAK;;CAGjD,MAAM,YAAY,MAA4C;AAC7D,SAAO,KAAK,UAAU,eAAe,KAAK;;CAG3C,MAAM,eAAe,MAA6B;AACjD,QAAM,KAAK,UAAU,kBAAkB,KAAK;;CAG7C,MAAM,WAAW,MAAgC;AAChD,QAAM,KAAK,UAAU,cAAc,KAAK;;CAGzC,MAAM,iBAAiB,aAAgD;AACtE,SAAO,KAAK,UAAU,oBAAoB,YAAY;;CAGvD,MAAM,kBAAkB,cAAiD;AACxE,SAAO,KAAK,UAAU,qBAAqB,aAAa;;CAGzD,MAAM,YAAY,aAAoC;AACrD,QAAM,KAAK,UAAU,eAAe,YAAY;;CAGjD,MAAM,gBAAgB,KAA4B;AACjD,QAAM,KAAK,UAAU,mBAAmB,IAAI;;CAG7C,MAAM,WAAW,UAAkB,UAAyC;AAC3E,QAAM,KAAK,UAAU,cAAc,UAAU,SAAS;;CAGvD,MAAM,UAAU,UAAkD;AACjE,SAAO,KAAK,UAAU,aAAa,SAAS;;CAG7C,MAAM,QAAQ,YAAoB,MAA8B;AAC/D,QAAM,KAAK,UAAU,WAAW,YAAY,KAAK;;CAGlD,MAAM,OAAO,YAA6C;AACzD,SAAO,KAAK,UAAU,UAAU,WAAW;;CAG5C,MAAM,UAAU,YAAmC;AAClD,QAAM,KAAK,UAAU,aAAa,WAAW;;CAG9C,MAAM,kBAAkB,OAAiC;AACxD,SAAO,KAAK,UAAU,qBAAqB,MAAM;;;;;;;AAQnD,SAAgB,YAAY,OAAmC;CAC9D,MAAM,YAAYC,eAAaC,MAAI;AAInC,QAAO,IAAI,qBAAqB;EAC/B,SAJe,IAAI,oBAAoB,UAAU;EAKjD,QAJc,WAAWA,MAAI;EAK7B,cAAc;EACd,WAAW;EAEX,YAAY,OAAO,aAAqB;AAEvC,OAAI,CADU,MAAM,QAAQ,UAAUA,MAAI,cAAc,CAC5C,QAAO;AACnB,UAAO;IACN,KAAKA,MAAI;IACT,QAAQA,MAAI;IACZ;;EAGF,mBAAmB,YAAqD;AAEvE,UADgB,MAAM,yBAAyB,WAAWA,MAAI,aAAa;;EAI5E,eAAe,OAAO,UAAU,cAAsB;AAOrD,OAAI,EANW,MAAM,4BACpB,WACAA,MAAI,cACJ,UACA,UACA,EACW,QAAS,QAAO;AAC5B,UAAO;IACN,KAAKA,MAAI;IACT,QAAQA,MAAI;IACZ;;EAEF,CAAC;;AAIH,IAAIC;;;;;;;;;;;;;AAcJ,SAAgB,eACf,iBACC;AAED,kBAAe;CAEf,MAAM,QAAQ,IAAI,MAA4B;AAG9C,OAAM,IAAI,4CAA4C,MAAM;AAE3D,SADiB,YAAY,EAAE,IAAI,CACnB,gBAAgB;GAC/B;AAGF,OAAM,IAAI,0CAA0C,MAAM;EACzD,MAAM,SAAS,WAAW,EAAE,IAAI;AAChC,SAAO,EAAE,KAAK;GACb,UAAU;GACV,uBAAuB,CAAC,OAAO;GAC/B,kBAAkB;IACjB;IACA;IACA;IACA;GACD,CAAC;GACD;AAGF,OAAM,IAAI,oBAAoB,OAAO,MAAM;EAO1C,MAAM,KAAK,EAAE,IAAI,OAAO,aAAa,IAAI;AACzC,MACC,2EAA2E,KAC1E,GACA,CAED,QAAO,EAAE,KAAK;;;;;;;;;;;;;SAaR;AAGP,SADiB,YAAY,EAAE,IAAI,CACnB,gBAAgB,EAAE,IAAI,IAAI;GACzC;AAEF,OAAM,KAAK,oBAAoB,OAAO,MAAM;AAE3C,SADiB,YAAY,EAAE,IAAI,CACnB,gBAAgB,EAAE,IAAI,IAAI;GACzC;AAGF,OAAM,KAAK,uBAAuB,OAAO,MAAM;AAE9C,SADiB,YAAY,EAAE,IAAI,CACnB,kBAAkB,EAAE,IAAI,IAAI;GAC3C;AAGF,OAAM,KAAK,gBAAgB,OAAO,MAAM;AAEvC,SADiB,YAAY,EAAE,IAAI,CACnB,YAAY,EAAE,IAAI,IAAI;GACrC;AAGF,OAAM,KAAK,cAAc,OAAO,MAAM;AAErC,SADiB,YAAY,EAAE,IAAI,CACnB,UAAU,EAAE,IAAI,IAAI;GACnC;AAIF,OAAM,IAAI,mBAAmB,OAAO,MAAM;EAEzC,MAAM,YAAY,MADD,YAAY,EAAE,IAAI,CACF,kBAAkB,EAAE,IAAI,IAAI;AAE7D,MAAI,CAAC,UACJ,QAAO,EAAE,KACR;GAAE,OAAO;GAAiB,mBAAmB;GAA4B,EACzE,IACA;AAKF,SAAO,EAAE,KAAK;GACb,KAAK,UAAU;GACf,oBAAoB,EAAE,IAAI;GAC1B,CAAC;GACD;AAGF,OAAM,KAAK,iBAAiB,OAAO,MAAM;EAGxC,MAAM,cAAc,EAAE,IAAI,OAAO,eAAe,IAAI;EACpD,IAAIC;AAEJ,MAAI;AACH,OAAI,YAAY,SAAS,mBAAmB,CAE3C,UADa,MAAM,EAAE,IAAI,MAAM,EAClB;YACH,YAAY,SAAS,oCAAoC,EAAE;IACrE,MAAM,OAAO,MAAM,EAAE,IAAI,MAAM;AAE/B,YADe,OAAO,YAAY,IAAI,gBAAgB,KAAK,CAAC,SAAS,CAAC,CACvD;cACL,CAAC,YAEX,SAAQ;OAER,QAAO,EAAE,KACR;IACC,OAAO;IACP,mBACC;IACD,EACD,IACA;UAEK;AACP,UAAO,EAAE,KACR;IAAE,OAAO;IAAmB,mBAAmB;IAAgC,EAC/E,IACA;;AAGF,MAAI,CAAC,MAEJ,QAAO,EAAE,KAAK,EAAE,CAAC;EAIlB,MAAM,YAAYH,eAAa,EAAE,IAAI;AAGrC,QAAM,UAAU,eAAe,MAAM;EAGrC,MAAM,YAAY,MAAM,UAAU,qBAAqB,MAAM;AAC7D,MAAI,UACH,OAAM,UAAU,eAAe,UAAU,YAAY;AAItD,SAAO,EAAE,KAAK,EAAE,CAAC;GAChB;AAEF,QAAO;;;;;AC3SR,eAAsB,YACrB,GACA,MAC2B;CAC3B,MAAM,OAAO,EAAE,IAAI,OAAO,gBAAgB;AAE1C,KAAI,CAAC,KACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,KAAK,WAAW,QAAQ,EAAE;EAI7B,MAAM,YAAY,MAHD,YAAY,EAAE,IAAI,CAGF,kBAAkB,EAAE,IAAI,IAAI;AAC7D,MAAI,CAAC,UACJ,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS;GACT,EACD,IACA;AAGF,IAAE,IAAI,QAAQ;GAAE,KAAK,UAAU;GAAK,OAAO,UAAU;GAAO,CAAC;AAC7D,SAAO,MAAM;;AAId,KAAI,CAAC,KAAK,WAAW,UAAU,CAC9B,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAGF,MAAM,QAAQ,KAAK,MAAM,EAAE;AAG3B,KAAI,UAAU,EAAE,IAAI,YAAY;AAC/B,IAAE,IAAI,QAAQ;GAAE,KAAK,EAAE,IAAI;GAAK,OAAO;GAAsB,CAAC;AAC9D,SAAO,MAAM;;CAGd,MAAM,aAAa,WAAW,EAAE,IAAI;AAIpC,KAAI;EACH,MAAM,UAAU,MAAM,kBACrB,OACA,EAAE,IAAI,YACN,WACA;AAGD,MAAI,QAAQ,QAAQ,EAAE,IAAI,IACzB,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS;GACT,EACD,IACA;AAIF,IAAE,IAAI,QAAQ;GAAE,KAAK,QAAQ;GAAK,OAAO,QAAQ;GAAiB,CAAC;AACnE,SAAO,MAAM;UACL,KAAK;AAGb,MAAI,eAAe,kBAClB,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,IAAI;GACb,EACD,IACA;;AAOH,KAAI;EACH,MAAM,UAAU,MAAM,iBACrB,OACA,EAAE,IAAI,aACN,YACA,EAAE,IAAI,IACN;AAGD,IAAE,IAAI,QAAQ;GAAE,KAAK,QAAQ;GAAK,OAAO,QAAQ,OAAO;GAAW,CAAC;AACpE,SAAO,MAAM;SACN;AAIR,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;;;;;;;;;;;ACrHF,MAAMI,kBAAgB;AACtB,MAAM,aAAa;;;;;;AAgBnB,MAAMC,kBAAgC,OAAO,SAAS,WAAW,MAAM,OAAO,KAAK;AAEnF,IAAa,cAAb,MAAyB;CACxB,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,OAAwB,EAAE,EAAE;AACvC,OAAK,UAAU,KAAK,WAAW;AAC/B,OAAK,QAAQ,KAAK;AAElB,OAAK,WAAW,IAAI,6BAA6B,EAChD,SAAS;GACR,KAAK,IAAI,uBAAuB;IAC/B,QAAQ,KAAK,UAAUD;IACvB,OAAO;IACP,CAAC;GACF,KAAK,IAAI,uBAAuB,EAC/B,OAAO,gBACP,CAAC;GACF,EACD,CAAC;;CAGH,MAAM,QAAQ,KAA0C;AAEvD,MAAI,KAAK,OAAO;GACf,MAAM,SAAS,MAAM,KAAK,MAAM,WAAW,IAAI;AAC/C,OAAI,UAAU,CAAC,OAAO,SAAS;AAE9B,QAAI,OAAO,MACV,MAAK,MAAM,aAAa,WAAW,KAAK,eAAe,IAAI,EAAE,OAAO;AAErE,WAAO,OAAO;;;EAIhB,MAAM,MAAM,MAAM,KAAK,eAAe,IAAI;AAG1C,MAAI,OAAO,KAAK,MACf,OAAM,KAAK,MAAM,SAAS,KAAK,IAAI;WACzB,CAAC,OAAO,KAAK,MACvB,OAAM,KAAK,MAAM,WAAW,IAAI;AAGjC,SAAO;;CAGR,MAAc,eAAe,KAA0C;EAEtE,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,KAAK,QAAQ;AAEpE,MAAI;GAEH,MAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,KAA2B,EAClE,QAAQ,WAAW,QACnB,CAAC;AAEF,OAAI,IAAI,OAAO,IACd,QAAO;AAER,UAAO;UACA;AACP,UAAO;YACE;AACT,gBAAa,UAAU;;;;;;;;;;AC/D1B,MAAM,YAAY,OAAU;AAC5B,MAAM,UAAU,OAAU,KAAK;AAE/B,IAAa,kBAAb,MAAiD;CAChD,AAAQ;CAER,cAAc;AACb,OAAK,QAAQ,OAAO;;CAGrB,AAAQ,YAAY,KAAqB;AAExC,SAAO,8BAA8B,mBAAmB,IAAI;;CAG7D,MAAM,SACL,KACA,KACA,aACgB;EAChB,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,IAAI,SAAS,KAAK,UAAU,IAAI,EAAE,EAClD,SAAS;GACR,gBAAgB;GAChB,iBAAiB;GACjB,eAAe,KAAK,KAAK,CAAC,UAAU;GACpC,EACD,CAAC;AAEF,QAAM,KAAK,MAAM,IAAI,UAAU,SAAS;;CAGzC,MAAM,WAAW,KAA0C;EAC1D,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,MAAM,KAAK,MAAM,MAAM,SAAS;AAEjD,MAAI,CAAC,SACJ,QAAO;EAGR,MAAM,WAAW,SAAS,SAAS,QAAQ,IAAI,cAAc,IAAI,KAAK,GAAG;EAEzE,MAAM,MADM,KAAK,KAAK,GACJ;EAGlB,MAAM,SAAS,KAAK,YAAY,IAAI,MAAM,SAAS,MAAM,CAAC;AAC1D,MAAI,CAAC,OAAO,MAAM,OAAO,MAAM,OAAO,KAAK;AAC1C,SAAM,KAAK,WAAW,IAAI;AAC1B,UAAO;;AAGR,SAAO;GACN;GACA,KAAK,OAAO;GACZ,WAAW;GACX,OAAO,MAAM;GACb,SAAS,MAAM;GACf;;CAGF,MAAM,aACL,KACA,QACA,aACgB;AAEhB,YACC,QAAQ,CAAC,MAAM,QAAQ;AACtB,OAAI,IACH,QAAO,KAAK,SAAS,KAAK,IAAI;IAE9B,CACF;;CAGF,MAAM,WAAW,KAA4B;EAC5C,MAAM,WAAW,KAAK,YAAY,IAAI;AACtC,QAAM,KAAK,MAAM,OAAO,SAAS;;CAGlC,MAAM,QAAuB;;;;;;;;;;AClG9B,SAAgB,iBACf,QAC4C;CAC5C,MAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,KAAI,MAAM,WAAW,EACpB,QAAO;CAGR,MAAM,CAAC,KAAK,aAAa;AACzB,KAAI,CAAC,KAAK,WAAW,OAAO,IAAI,CAAC,UAChC,QAAO;AAGR,QAAO;EAAE;EAAK;EAAW;;;;;;AAO1B,eAAsB,gBACrB,GACA,eACA,cACoB;CAEpB,MAAM,MAAM,IAAI,IAAI,EAAE,IAAI,IAAI;CAC9B,MAAM,MAAM,IAAI,SAAS,QAAQ,UAAU,GAAG;AAG9C,KAAI,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,CAC3C,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAIF,MAAM,cAAc,EAAE,IAAI,OAAO,gBAAgB;CACjD,IAAIE;CACJ,IAAIC;AAEJ,KAAI,aAAa;EAEhB,MAAM,SAAS,iBAAiB,YAAY;AAC5C,MAAI,CAAC,OACJ,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,wCAAwC;GACjD,EACD,IACA;AAGF,MAAI;GAEH,MAAM,SAAS,MAAMC,cAAY,QAAQ,OAAO,IAAI;AACpD,OAAI,CAAC,OACJ,QAAO,EAAE,KACR;IACC,OAAO;IACP,SAAS,kBAAkB,OAAO;IAClC,EACD,IACA;GAOF,MAAM,WAAW,0BAA0B,QAAQ,EAAE,IAHnC,OAAO,UAAU,WAAW,IAAI,GAC/C,OAAO,YACP,IAAI,OAAO,aACsE,CAAC;AAErF,OAAI,CAAC,SACJ,QAAO,EAAE,KACR;IACC,OAAO;IACP,SAAS,sCAAsC,OAAO;IACtD,EACD,IACA;AAIF,iBAAc,OAAO;AACrB,eAAY,IAAI,IAAI,SAAS;AAC7B,OAAI,UAAU,aAAa,SAC1B,QAAO,EAAE,KACR;IACC,OAAO;IACP,SAAS;IACT,EACD,IACA;AAEF,aAAU,WAAW,IAAI;AACzB,aAAU,SAAS,IAAI;WACf,KAAK;AACb,UAAO,EAAE,KACR;IACC,OAAO;IACP,SAAS,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvF,EACD,IACA;;QAEI;EAGN,MAAM,SAAS,IAAI,WAAW,aAAa;AAC3C,gBAAc,SAAS,0BAA0B;EACjD,MAAM,WAAW,SAAS,0BAA0B;AAGpD,cAAY,IAAI,IAAI,SAAS,MAAM,IAAI,UAAU,SAAS;;CAI3D,IAAIC,UAAkC,EAAE;CACxC,MAAM,OAAO,EAAE,IAAI,OAAO,gBAAgB;CAC1C,IAAIC;AAEJ,KAAI,MAAM,WAAW,QAAQ,CAE5B,KAAI;EAEH,MAAM,YAAY,MADD,YAAY,EAAE,IAAI,CACF,kBAAkB,EAAE,IAAI,IAAI;AAC7D,MAAI,UACH,WAAU,UAAU;SAEd;UAGE,MAAM,WAAW,UAAU,EAAE;EACvC,MAAM,QAAQ,KAAK,MAAM,EAAE;EAC3B,MAAM,aAAa,WAAW,EAAE,IAAI;AAEpC,MAAI;AAEH,OAAI,UAAU,EAAE,IAAI,WACnB,WAAU,EAAE,IAAI;QACV;IAEN,MAAM,UAAU,MAAM,kBACrB,OACA,EAAE,IAAI,YACN,WACA;AACD,QAAI,QAAQ,IACX,WAAU,QAAQ;;WAGZ,KAAK;AAGb,OAAI,eAAe,kBAClB,QAAO,EAAE,KACR;IACC,OAAO;IACP,SAAS,IAAI;IACb,EACD,IACA;;;AAOJ,KAAI,QACH,KAAI;EACH,MAAM,UAAU,MAAMC,cAAY;AAOlC,UAAQ,mBAAmB,UANR,MAAM,iBAAiB;GACzC,KAAK;GACL,KAAK;GACL;GACA;GACA,CAAC;SAEK;CAOT,MAAM,iBAAiB,IAAI,QAAQ,EAAE,IAAI,IAAI,QAAQ;AAerD,MAAK,MAAM,UAZa;EACvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,CAGA,gBAAe,OAAO,OAAO;AAI9B,KAAI,QAAQ,iBACX,gBAAe,IAAI,iBAAiB,QAAQ,iBAAiB;CAG9D,MAAMC,UAAuB;EAC5B,QAAQ,EAAE,IAAI;EACd,SAAS;EACT;AAGD,KAAI,EAAE,IAAI,WAAW,SAAS,EAAE,IAAI,WAAW,OAC9C,SAAQ,OAAO,EAAE,IAAI,IAAI;AAG1B,QAAO,MAAM,UAAU,UAAU,EAAE,QAAQ;;;;;;;;;AC/O5C,SAAgB,kBAAkB,OAAkC;AAEnE,KAAI,MAAM,UAAU,IAOnB;MANa,OAAO,aACnB,MAAM,IACN,MAAM,IACN,MAAM,IACN,MAAM,GACN,KACY,QAAQ;GAEpB,MAAM,QAAQ,OAAO,aACpB,MAAM,IACN,MAAM,IACN,MAAM,KACN,MAAM,IACN;AACD,OACC,UAAU,UACV,UAAU,UACV,UAAU,UACV,UAAU,UACV,UAAU,OAEV,QAAO;AAER,OAAI,UAAU,UAAU,UAAU,UAAU,UAAU,OACrD,QAAO;AAER,OAAI,UAAU,OACb,QAAO;AAGR,UAAO;;;AAKT,KAAI,MAAM,OAAO,OAAQ,MAAM,OAAO,OAAQ,MAAM,OAAO,IAC1D,QAAO;AAIR,KACC,MAAM,OAAO,OACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,GAEb,QAAO;AAIR,KAAI,MAAM,OAAO,MAAQ,MAAM,OAAO,MAAQ,MAAM,OAAO,GAC1D,QAAO;AAIR,KACC,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,QAAQ,MACd,MAAM,QAAQ,GAEd,QAAO;AAIR,KACC,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,OACb,MAAM,OAAO,IAEb,QAAO;AAGR,QAAO;;;;;AC/ER,eAAsB,QACrB,GACA,WACoB;CACpB,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;AAE9B,KAAI,CAAC,IACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,CAAC,MAAM,IAAI,CACd,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,IACA;AAGF,KAAI,QAAQ,EAAE,IAAI,IACjB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,iCAAiC;EAC1C,EACD,IACA;CAGF,MAAM,WAAW,MAAM,UAAU,eAAe;AAEhD,QAAO,IAAI,SAAS,UAAU;EAC7B,QAAQ;EACR,SAAS;GACR,gBAAgB;GAChB,kBAAkB,SAAS,OAAO,UAAU;GAC5C;EACD,CAAC;;AAGH,eAAsB,cACrB,GACA,WACoB;CACpB,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;AAE9B,KAAI,CAAC,IACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,CAAC,MAAM,IAAI,CACd,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,IACA;AAGF,KAAI,QAAQ,EAAE,IAAI,IACjB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,iCAAiC;EAC1C,EACD,IACA;CAGF,MAAM,OAAO,MAAM,UAAU,kBAAkB;AAE/C,QAAO,EAAE,KAAK;EACb,KAAK,KAAK;EACV,QAAQ;EACR,QAAQ;EACR,KAAK,KAAK;EACV,CAAC;;AAGH,eAAsB,UACrB,GACA,WACoB;CAEpB,MAAM,OAAO,MAAM,UAAU,kBAAkB;AAE/C,QAAO,EAAE,KAAK,EACb,OAAO,CACN;EACC,KAAK,KAAK;EACV,MAAM,KAAK;EACX,KAAK,KAAK;EACV,QAAQ;EACR,CACD,EACD,CAAC;;AAGH,eAAsB,UACrB,GACA,YACoB;CACpB,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;AAE9B,KAAI,CAAC,IACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,CAAC,MAAM,IAAI,CACd,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,IACA;AAGF,KAAI,QAAQ,EAAE,IAAI,IACjB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,iCAAiC;EAC1C,EACD,IACA;AAIF,KAAI,CAAC,EAAE,IAAI,MAEV,QAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;CAI5B,MAAM,SAAS,GAAG,IAAI;CACtB,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;CACpC,MAAM,QAAQ,KAAK,IAAI,OAAO,EAAE,IAAI,MAAM,QAAQ,CAAC,IAAI,KAAK,IAAK;CAEjE,MAAM,SAAS,MAAM,EAAE,IAAI,MAAM,KAAK;EACrC;EACA;EACA,QAAQ,UAAU;EAClB,CAAC;CAKF,MAAMC,SAA8C,EAAE,MAFzC,OAAO,QAAQ,KAAK,QAAQ,IAAI,IAAI,MAAM,OAAO,OAAO,CAAC,EAEV;AAC5D,KAAI,OAAO,aAAa,OAAO,OAC9B,QAAO,SAAS,OAAO;AAGxB,QAAO,EAAE,KAAK,OAAO;;AAGtB,eAAsB,UACrB,GACA,WACoB;CACpB,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;CAC9B,MAAM,YAAY,EAAE,IAAI,QAAQ,OAAO;AAEvC,KAAI,CAAC,IACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,KAAI,CAAC,aAAa,UAAU,WAAW,EACtC,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,CAAC,MAAM,IAAI,CACd,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,IACA;AAGF,KAAI,QAAQ,EAAE,IAAI,IACjB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,iCAAiC;EAC1C,EACD,IACA;CAGF,MAAM,WAAW,MAAM,UAAU,aAAa,UAAU;AAExD,QAAO,IAAI,SAAS,UAAU;EAC7B,QAAQ;EACR,SAAS;GACR,gBAAgB;GAChB,kBAAkB,SAAS,OAAO,UAAU;GAC5C;EACD,CAAC;;AAGH,eAAsB,QACrB,GACA,YACoB;CACpB,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;CAC9B,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;AAE9B,KAAI,CAAC,OAAO,CAAC,IACZ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,CAAC,MAAM,IAAI,CACd,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,IACA;AAGF,KAAI,QAAQ,EAAE,IAAI,IACjB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,iCAAiC;EAC1C,EACD,IACA;AAIF,KAAI,CAAC,EAAE,IAAI,MACV,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAIF,MAAM,MAAM,GAAG,IAAI,GAAG;CACtB,MAAM,OAAO,MAAM,EAAE,IAAI,MAAM,IAAI,IAAI;AAEvC,KAAI,CAAC,KACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,mBAAmB;EAC5B,EACD,IACA;CAIF,IAAI,cAAc,KAAK,cAAc;AAGrC,KAAI,CAAC,eAAe,gBAAgB,OAAO;EAE1C,MAAM,CAAC,cAAc,cAAc,KAAK,KAAK,KAAK;EAClD,MAAM,SAAS,aAAa,WAAW;EACvC,MAAM,EAAE,OAAO,gBAAgB,MAAM,OAAO,MAAM;AAClD,SAAO,aAAa;AAEpB,MAAI,eAAe,YAAY,UAAU,GACxC,eACC,kBAAkB,YAAY,IAAI;MAEnC,eAAc;AAGf,SAAO,IAAI,SAAS,YAAY;GAC/B,QAAQ;GACR,SAAS;IACR,gBAAgB;IAChB,kBAAkB,KAAK,KAAK,UAAU;IACtC;GACD,CAAC;;AAGH,QAAO,IAAI,SAAS,KAAK,MAAM;EAC9B,QAAQ;EACR,SAAS;GACR,gBAAgB;GAChB,kBAAkB,KAAK,KAAK,UAAU;GACtC;EACD,CAAC;;AAGH,eAAsBC,YACrB,GACA,WACoB;CACpB,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM;CAC9B,MAAM,aAAa,EAAE,IAAI,MAAM,aAAa;CAC5C,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO;AAEhC,KAAI,CAAC,IACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,KAAI,CAAC,WACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,KAAI,CAAC,KACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,CAAC,MAAM,IAAI,CACd,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,IACA;AAIF,KAAI,CAAC,OAAO,WAAW,CACtB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAIF,KAAI,CAAC,YAAY,KAAK,CACrB,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAuB,EAC3D,IACA;AAIF,KAAI,QAAQ,EAAE,IAAI,IACjB,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS,iCAAiC;EAC1C,EACD,IACA;AAGF,KAAI;EACH,MAAM,WAAW,MAAM,UAAU,kBAAkB,YAAY,KAAK;AAEpE,SAAO,IAAI,SAAS,UAAU;GAC7B,QAAQ;GACR,SAAS;IACR,gBAAgB;IAChB,kBAAkB,SAAS,OAAO,UAAU;IAC5C;GACD,CAAC;UACM,KAAK;AAGb,UAAQ,MAAM,+BAA+B,IAAI;AACjD,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS;GACT,EACD,IACA;;;;;;;;;;AC/YH,MAAMC,gBAA4C;CACjD,0BAA0B,oBAAoB;CAC9C,2BAA2B,qBAAqB;CAChD,sBAAsB,gBAAgB;CACtC,sBAAsB,gBAAgB;CACtC,0BAA0B,oBAAoB;CAC9C,wBAAwB,kBAAkB;CAC1C,4BAA4B,sBAAsB;CAClD,wBAAwB,kBAAkB;CAC1C,yBAAyB,mBAAmB;CAC5C,uBAAuB,iBAAiB;CACxC,4BAA4B,sBAAsB;CAClD,2BAA2B,qBAAqB;CAChD,8BAA8B,wBAAwB;CACtD,+BAA+B,yBAAyB;CACxD,4BAA4B,sBAAsB;CAClD;;;;;;;;;;;;AAaD,IAAa,kBAAb,MAA6B;CAC5B,AAAQ;CAER,YAAY,UAAgC,EAAE,EAAE;AAC/C,OAAK,aAAa,QAAQ,UAAU;;;;;;;;;CAUrC,eAAe,YAAoB,QAAuB;EACzD,MAAM,SAAS,cAAc;AAE7B,MAAI,CAAC,QAAQ;AAEZ,OAAI,KAAK,WACR,OAAM,IAAI,MACT,4CAA4C,WAAW,mDACvD;AAEF;;AAGD,MAAI;AACH,SAAM,QAAQ,OAAO;WACb,OAAO;AACf,OAAI,iBAAiB,gBACpB,OAAM,IAAI,MACT,iCAAiC,WAAW,IAAI,MAAM,UACtD;AAEF,SAAM;;;;;;CAOR,UAAU,YAA6B;AACtC,SAAO,cAAc;;;;;CAMtB,mBAA6B;AAC5B,SAAO,OAAO,KAAK,cAAc;;;;;;;AAQnC,MAAa,YAAY,IAAI,gBAAgB,EAAE,QAAQ,OAAO,CAAC;;;;ACzG/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,CAAC,MAAM,KAAK,CACf,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,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,CAAC,MAAM,KAAK,CACf,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,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,QAAQ,KAAK,GAAG,WAAW,GAAG;EACnC,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,CAAC,MAAM,KAAK,CACf,QAAO,EAAE,KACR;EAAE,OAAO;EAAkB,SAAS;EAAsB,EAC1D,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,IAAI,cAAc,EAAE,IAAI,OAAO,eAAe;CAE9C,MAAM,QAAQ,IAAI,WAAW,MAAM,EAAE,IAAI,aAAa,CAAC;AACvD,KAAI,CAAC,eAAe,gBAAgB,MACnC,eAAc,kBAAkB,MAAM,IAAI;CAI3C,MAAM,gBAAgB,KAAK,OAAO;AAClC,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;;;;;AC3iBtB,eAAsB,eAAe,GAAuC;AAC3E,QAAO,EAAE,KAAK;EACb,KAAK,EAAE,IAAI;EACX,sBAAsB,EAAE;EACxB,oBAAoB;EACpB,CAAC;;;;;AAMH,eAAsB,cACrB,GACA,WACoB;CAMpB,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;CAED,MAAM,EAAE,OAAO,gBAAgB,MAAM,UAAU,aAAa;CAC5D,MAAM,QAAQ,eAAe,EAAE,IAAI;AAEnC,QAAO,EAAE,KAAK;EACb;EACA;EACA,QAAQ,EAAE,IAAI;EACd,KAAK,EAAE,IAAI;EACX,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;EAC1B,gBAAgB;EAChB,QAAQ;EACR,CAAC;;;;;AAMH,eAAsB,eACrB,GACA,WACoB;CACpB,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;EAED,MAAM,EAAE,OAAO,gBAAgB,MAAM,UAAU,aAAa;EAC5D,MAAM,QAAQ,eAAe,EAAE,IAAI;AAEnC,SAAO,EAAE,KAAK;GACb;GACA;GACA,QAAQ,EAAE,IAAI;GACd,KAAK,EAAE,IAAI;GACX,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;GAC1B,gBAAgB;GAChB,QAAQ;GACR,CAAC;UACM,KAAK;AAEb,MAAI,eAAe,kBAClB,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,IAAI;GACb,EACD,IACA;AAEF,SAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,eAAe,QAAQ,IAAI,UAAU;GAC9C,EACD,IACA;;;;;;AAOH,eAAsB,WACrB,GACA,WACoB;CACpB,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,YAAY;EAC/B,MAAM,EAAE,OAAO,gBAAgB,MAAM,UAAU,aAAa;EAC5D,MAAM,QAAQ,eAAe,EAAE,IAAI;AACnC,SAAO,EAAE,KAAK;GACb,QAAQ,EAAE,IAAI;GACd,KAAK,EAAE,IAAI;GACX,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;GAC1B,gBAAgB;GAChB,QAAQ;GACR,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;EAGF,MAAM,EAAE,OAAO,gBAAgB,MAAM,UAAU,aAAa;EAC5D,MAAM,QAAQ,eAAe,EAAE,IAAI;AACnC,SAAO,EAAE,KAAK;GACb,QAAQ,EAAE,IAAI;GACd,KAAK,EAAE,IAAI;GACX,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;GAC1B,gBAAgB;GAChB,QAAQ;GACR,CAAC;UACM,KAAK;AAGb,MAAI,eAAe,kBAClB,QAAO,EAAE,KACR;GACC,OAAO;GACP,SAAS,IAAI;GACb,EACD,IACA;AAEF,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,mBACrB,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;EAGH,MAAM,YAAY,UAAU,iBAAiB;AAE7C,SAAO,EAAE,KAAK;GACb;GACA;GACA,UAAU;GACV,YAAY,OAAO;GACnB,SAAS,OAAO;GAChB;GACA;GACA,oBAAoB;GACpB;GACA;GACA,CAAC;UACM,KAAK;AAEb,SAAO,EAAE,KAAK;GACb,WAAW;GACX,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,mBACrB,GACoB;AACpB,QAAO,EAAE,KAAK,EAAE,eAAe,OAAO,CAAC;;;;;;AAOxC,eAAsB,yBACrB,GACoB;AACpB,QAAO,EAAE,KAAK,EAAE,CAAC;;;;;AAMlB,eAAsB,YACrB,GACA,WACoB;CACpB,MAAM,OAAO,MAAM,EAAE,IAAI,MAAyB;AAElD,KAAI,CAAC,KAAK,MACT,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAGF,OAAM,UAAU,eAAe,KAAK,MAAM;AAC1C,QAAO,EAAE,KAAK,EAAE,CAAC;;;;;;AAOlB,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;;;;;;;;;;;;;;;;;;AC5cH,MAAM,eAAe,MADN,KAAK;;;;AAWpB,eAAe,SAAS,MAAc,QAAsC;CAC3E,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,MAAM,MAAM,OAAO,OAAO,UAC/B,OACA,QAAQ,OAAO,OAAO,EACtB;EAAE,MAAM;EAAQ,MAAM;EAAW,EACjC,OACA,CAAC,OAAO,CACR;AACD,QAAO,OAAO,OAAO,KAAK,QAAQ,KAAK,QAAQ,OAAO,KAAK,CAAC;;;;;AAM7D,eAAe,WACd,MACA,WACA,QACmB;CACnB,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,MAAM,MAAM,OAAO,OAAO,UAC/B,OACA,QAAQ,OAAO,OAAO,EACtB;EAAE,MAAM;EAAQ,MAAM;EAAW,EACjC,OACA,CAAC,SAAS,CACV;AACD,QAAO,OAAO,OAAO,OAAO,QAAQ,KAAK,WAAW,QAAQ,OAAO,KAAK,CAAC;;;;;;;;;AAW1E,eAAsB,qBACrB,KACA,WACkB;CAElB,MAAMC,UAAwB;EAAE;EAAK,KADzB,KAAK,OAAO,KAAK,KAAK,GAAG,gBAAgB,IAAK;EAChB;CAE1C,MAAM,aAAa,KAAK,UAAU,QAAQ;CAC1C,MAAM,aAAa,UAAU,OAAO,IAAI,aAAa,CAAC,OAAO,WAAW,CAAC;CAEzE,MAAM,YAAY,MAAM,SAAS,YAAY,UAAU;AAGvD,QAAO,GAAG,WAAW,GAFA,UAAU,OAAO,IAAI,WAAW,UAAU,CAAC;;;;;;;;;;AAajE,eAAsB,uBACrB,OACA,aACA,WAC+B;CAC/B,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,KAAI,MAAM,WAAW,EACpB,QAAO;CAGR,MAAM,CAAC,YAAY,gBAAgB;AAEnC,KAAI;AAIH,MAAI,CADY,MAAM,WAAW,YADV,UAAU,OAAO,aAAa,CACO,QAAQ,UAAU,CAE7E,QAAO;EAIR,MAAM,aAAa,IAAI,aAAa,CAAC,OAAO,UAAU,OAAO,WAAW,CAAC;EACzE,MAAMA,UAAwB,KAAK,MAAM,WAAW;AAGpD,MAAI,QAAQ,QAAQ,YACnB,QAAO;EAIR,MAAMC,QAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AACzC,MAAI,QAAQ,MAAMA,MACjB,QAAO;AAGR,SAAO;SACA;AACP,SAAO;;;;;;ACzGT,MAAM,gBAAgB;;;;;;;;;;AAsCtB,eAAsB,6BACrB,GACoB;AAIpB,QAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,CAAC;;;;;;;;;;AAW3C,eAAsB,iBACrB,GACoB;CACpB,MAAM,OAAO,MAAM,EAAE,IAAI,MAMrB;CAEJ,MAAM,EAAE,UAAU;AAElB,KAAI,CAAC,MACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;AAUF,KAAI,CANY,MAAM,uBACrB,OACA,EAAE,IAAI,KACN,EAAE,IAAI,WACN,CAGA,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAIF,MAAM,YAAY,MAAM,sBAAsB,EAAE,IAAI,IAAI;AACxD,KAAI,CAAC,UACJ,QAAO,EAAE,KACR;EACC,OAAO;EACP,SAAS;EACT,EACD,IACA;CAgBF,MAAM,WAAW,MAAM,cAZa;EACnC,MAAM;EACN,MAAM,UAAU;EAChB,cAAc,KAAK,gBAAgB,UAAU,UAAU;EACvD,aAAa,KAAK,eAAe,UAAU,UAAU;EACrD,qBACC,KAAK,uBAAuB,UAAU,UAAU;EACjD,UAAU,KAAK,YAAY,UAAU,UAAU;EAC/C,EAGe,MAAM,iBAAiB,OAAO,EAAE,IAAI,YAAY,CACZ;AAEpD,QAAO,EAAE,KAAK,EAAE,WAAW,UAAU,CAAC;;;;;AAMvC,eAAe,sBACd,KAC8B;AAC9B,KAAI;EACH,MAAM,MAAM,MAAM,MAAM,GAAG,cAAc,GAAG,IAAI,YAAY;AAC5D,MAAI,CAAC,IAAI,GACR,QAAO;AAIR,UAFa,MAAM,IAAI,MAAM,EAElB,QAAQ,OAAO,CAAC,GAAG,UAAU,CAAC,KAAK,IAAI;SAC3C;AACP,SAAO;;;;;;;;;;;AAYT,eAAe,cACd,IACA,SAC8B;CAE9B,MAAM,QAAQ,OAAO,GAAG;CAGxB,MAAM,MAAM,MAAM,QAAQ,KAAK,MAAM;AAGrC,QAAO;EACN,GAAG;EACH,KAAK,UAAU,OAAO,IAAI;EAC1B;;;;;;;;;;;AAYF,eAAsB,kBACrB,GACoB;CACpB,MAAM,QAAQ,MAAM,qBAAqB,EAAE,IAAI,KAAK,EAAE,IAAI,WAAW;AACrE,QAAO,EAAE,KAAK,EAAE,OAAO,CAAC;;;;;;;;;AC9LzB,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4HpC,eAAe,kBAAkB,QAAiC;CAEjE,MAAM,OADU,IAAI,aAAa,CACZ,OAAO,OAAO;CACnC,MAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK;CAC9D,MAAM,YAAY,MAAM,KAAK,IAAI,WAAW,WAAW,CAAC;AAExD,QAAO,WADY,KAAK,OAAO,aAAa,GAAG,UAAU,CAAC,CAC7B;;AAI9B,IAAIC,gCAAwD;;;;AAK5D,eAAe,uBAAwC;AACtD,KAAI,CAAC,8BACJ,iCAAgC,kBAC/B,4BACA;AAEF,QAAO;;;;;AAMR,eAAsB,kBAAmC;AAExD,QAAO,kCADY,MAAM,sBAAsB,CACK;;;;;AAMrD,MAAa,oBACZ;;;;AAKD,SAAS,WAAW,MAAsB;AACzC,QAAO,KACL,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,SAAS;;;;;AAe1B,SAAgB,8BAA8B,MAAgC;CAC7E,MAAM,EAAE,SAAS,OAAO,WAAW;CAGnC,MAAM,cAAc,WAAW,KAAK,UAAU,QAAQ,CAAC;CACvD,MAAM,YAAY,WAAW,MAAM;AAEnC,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBA6Ie,WAAW,OAAO,CAAC;;;;;;;;;;;;;;;;;uBAiBnB,WAAW,OAAO,CAAC;;;;;;;;;;yBAUjB,YAAY,gBAAgB,UAAU,IAAI,4BAA4B;;;;;;;AAQ/F,SAAgB,uBACf,OACA,aACS;AACT,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsED,WAAW,YAAY,CAAC;sCACO,WAAW,MAAM,CAAC;;;;;;;;;;;;AE3axD,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,CAAC,MAAMA,MAAI,IAAI,CAClB,OAAM,IAAI,MAAM,uBAAuBA,MAAI,MAAM;AAElD,IAAI,CAAC,SAASA,MAAI,OAAO,CACxB,OAAM,IAAI,MAAM,0BAA0BA,MAAI,SAAS;AAGxD,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,MAAM,WAAWA,MAAI;AAGrB,KAAI,aAAa,MAAM;EACtB,MAAM,YAAYA,MAAI,QAAQ,aAAa,KAAK;AAChD,SAAO,UAAU,IAAI,UAAU,WAAW,UAAU,CAAC;;CAItD,MAAM,KAAKA,MAAI,QAAQ,WAAW,UAAU;AAC5C,KAAI,YAAY,aAAa,OAC5B,QAAOA,MAAI,QAAQ,IAAI,IAAI,EAAE,cAAc,UAAU,CAAC;AAGvD,QAAOA,MAAI,QAAQ,IAAI,GAAG;;AAI3B,IAAI,IAAI,0BAA0B,MAAM;CACvC,MAAM,cAAc;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,KAAK,YAAY;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,iBAAiB,OAAO,MAAM;AACrC,KAAI;AAEH,QADkB,aAAa,EAAE,IAAI,CACrB,gBAAgB;AAChC,SAAO,EAAE,KAAK;GAAE,QAAQ;GAAM;GAAS,CAAC;SACjC;AACP,SAAO,EAAE,KAAK;GAAE,QAAQ;GAAa;GAAS,EAAE,IAAI;;EAEpD;AAGF,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,MAC1CG,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;AACD,IAAI,IAAI,qCAAqC,MAC5CC,YAAe,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,uCAAuC,OAAO,GAAG,SAAS;CACjE,MAAM,gBAAgB,EAAE,IAAI,MAAM,OAAO;AACzC,KAAI,CAAC,iBAAiB,kBAAkB,EAAE,IAAI,IAC7C,QAAOC,aAAkB,GAAG,aAAa,EAAE,IAAI,CAAC;AAEjD,OAAM,MAAM;EACX;AAEF,IAAI,IAAI,oCAAoC,OAAO,GAAG,SAAS;CAC9D,MAAM,gBAAgB,EAAE,IAAI,MAAM,OAAO;AACzC,KAAI,CAAC,iBAAiB,kBAAkB,EAAE,IAAI,IAC7C,QAAOC,UAAe,GAAG,aAAa,EAAE,IAAI,CAAC;AAE9C,OAAM,MAAM;EACX;AAEF,IAAI,IAAI,sCAAsC,OAAO,GAAG,SAAS;CAChE,MAAM,gBAAgB,EAAE,IAAI,MAAM,OAAO;AACzC,KAAI,CAAC,iBAAiB,kBAAkB,EAAE,IAAI,IAC7C,QAAOC,YAAiB,GAAG,aAAa,EAAE,IAAI,CAAC;AAEhD,OAAM,MAAM;EACX;AAGF,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;AAIF,IAAI,KACH,2DACA,aACAC,6BACA;AACD,IAAI,KACH,+CACA,aACAC,iBACA;AACD,IAAI,IACH,8CACA,aACAC,kBACA;AAGD,IAAI,KAAK,2CAA2C,MACnDC,cAAqB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC5C;AACD,IAAI,KAAK,4CAA4C,MACpDC,eAAsB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC7C;AACD,IAAI,IAAI,wCAAwC,MAC/CC,WAAkB,GAAG,aAAa,EAAE,IAAI,CAAC,CACzC;AACD,IAAI,KAAK,0CAA0CC,cAAqB;AAGxE,IAAI,IAAI,+CAA+C,cAAc,MACpEC,mBAA0B,GAAG,aAAa,EAAE,IAAI,CAAC,CACjD;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;AACD,IAAI,KACH,+CACA,aACAC,mBACA;AACD,IAAI,KACH,qDACA,aACAC,yBACA;AACD,IAAI,KAAK,wCAAwC,cAAc,MAC9DC,YAAmB,GAAG,aAAa,EAAE,IAAI,CAAC,CAC1C;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,8CAA8C,aAAa,OAAO,MAAM;CAEhF,MAAM,SAAS,MADG,aAAa,EAAE,IAAI,CACN,qBAAqB,EAAE,IAAI,OAAO;AACjE,QAAO,EAAE,KAAK,OAAO;EACpB;AAGF,IAAI,IACH,8CACA,aACA,OAAO,MAAM;CACZ,MAAM,YAAY,aAAa,EAAE,IAAI;AACrC,QAAO,EAAE,KAAK,MAAM,UAAU,sBAAsB,CAAC;EAEtD;AAOD,IAAI,KAAK,iBAAiB,aAAa,OAAO,MAAM;CACnD,MAAM,YAAY,aAAa,EAAE,IAAI;CACrC,MAAM,OAAO,MAAM,EAAE,IAAI,MAAyB,CAAC,aAAa,EAAE,EAAuB;AACzF,KAAI;EACH,MAAM,SAAS,MAAMC,wBACpB,WACA,EAAE,IAAI,cACN,EAAE,IAAI,KACN,KAAK,KACL;AACD,SAAO,EAAE,KAAK,OAAO;UACb,KAAK;AACb,UAAQ,MAAM,uBAAuB,IAAI;EACzC,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,EAAE,KAAK;GAAE,OAAO;GAAqB;GAAS,EAAE,IAAI;;EAE3D;AAGF,IAAI,IAAI,qBAAqB,OAAO,MAAM;CACzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;AAClC,KAAI,CAAC,MACJ,QAAO,EAAE,KACR,uBAAuB,iBAAiB,kCAAkC,EAC1E,KACA,EAAE,2BAA2B,mBAAmB,CAChD;CAGF,MAAM,YAAY,aAAa,EAAE,IAAI;CACrC,MAAM,UAAU,MAAMC,uBACrB,WACA,EAAE,IAAI,cACN,EAAE,IAAI,KACN,MACA;AAED,KAAI,CAAC,QACJ,QAAO,EAAE,KACR,uBAAuB,iBAAiB,yCAAyC,EACjF,KACA,EAAE,2BAA2B,mBAAmB,CAChD;CAGF,MAAM,MAAM,MAAM,iBAAiB;AACnC,QAAO,EAAE,KACR,8BAA8B;EAC7B;EACA;EACA,QAAQ,EAAE,IAAI;EACd,CAAC,EACF,KACA,EAAE,2BAA2B,KAAK,CAClC;EACA;AAGF,IAAI,KAAK,qBAAqB,OAAO,MAAM;CAC1C,MAAM,OAAO,MAAM,EAAE,IAAI,MAGrB;AAEJ,KAAI,CAAC,KAAK,SAAS,CAAC,KAAK,SACxB,QAAO,EAAE,KAAK;EAAE,SAAS;EAAO,OAAO;EAA6B,EAAE,IAAI;CAG3E,MAAM,YAAY,aAAa,EAAE,IAAI;CAErC,MAAM,SAAS,MAAMC,4BACpB,WACA,EAAE,IAAI,cACN,KAAK,OACL,KAAK,SACL;AAED,KAAI,OAAO,QACV,QAAO,EAAE,KAAK,EAAE,SAAS,MAAM,CAAC;KAEhC,QAAO,EAAE,KAAK;EAAE,SAAS;EAAO,OAAO,OAAO;EAAO,EAAE,IAAI;EAE3D;AAGF,IAAI,IAAI,iBAAiB,aAAa,OAAO,MAAM;CAClD,MAAM,YAAY,aAAa,EAAE,IAAI;CACrC,MAAM,WAAW,MAAMC,aAAqB,UAAU;AACtD,QAAO,EAAE,KAAK,EAAE,UAAU,CAAC;EAC1B;AAGF,IAAI,KAAK,mBAAmB,aAAa,OAAO,MAAM;CACrD,MAAM,OAAO,MAAM,EAAE,IAAI,MAAsB;AAC/C,KAAI,CAAC,KAAK,GACT,QAAO,EAAE,KAAK;EAAE,SAAS;EAAO,OAAO;EAAsB,EAAE,IAAI;CAGpE,MAAM,YAAY,aAAa,EAAE,IAAI;CACrC,MAAM,UAAU,MAAMC,cAAsB,WAAW,KAAK,GAAG;AAC/D,QAAO,EAAE,KAAK,EAAE,SAAS,SAAS,CAAC;EAClC;AAGF,MAAM,WAAW,eAAe,aAAa;AAC7C,IAAI,MAAM,KAAK,SAAS;AAIxB,IAAI,IAAI,YAAY,MAAM,gBAAgB,GAAG,aAAa,WAAW,CAAC;AAEtE,kBAAe"}
1
+ {"version":3,"file":"index.js","names":["result: Record<string, unknown>","encode","atcuteEncode","decode","atcuteDecode","sql: SqlStorage","missing: CID[]","blobs: Array<{ cid: string; recordUri: string }>","sql: SqlStorage","now","sql: SqlStorage","eventPayload: Omit<CommitEvent, \"seq\">","cborEncode","events: SeqEvent[]","cborDecode","r2: R2Bucket","did: string","cidToString","createCid","env","tidNow","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 }>","carChunks: Uint8Array[]","cborEncode","result: Record<string, unknown>","cids: string[]","cachedKeypair: Secp256k1Keypair | null","cachedSigningKey: string | null","result: Partial<T>","payload: ServiceJwtPayload","now","payload: JWTPayload","protectedHeader: { typ?: string }","accountDO: DurableObjectStub<AccountDurableObject>","getAccountDO","env","getAccountDO: (env: PDSEnv) => DurableObjectStub<AccountDurableObject>","token: string | undefined","PLC_DIRECTORY","stubbableFetch: typeof fetch","audienceDid: string","targetUrl: URL","didResolver","headers: Record<string, string>","userDid: string | undefined","getKeypair","reqInit: RequestInit","result: { cids: string[]; cursor?: string }","getRecord","recordSchemas: Record<string, BaseSchema>","verifyPassword","payload: TokenPayload","now","registrationScriptHashPromise: Promise<string> | null","env","_env","keypairPromise: Promise<Secp256k1Keypair> | null","sync.getRepo","sync.getRepoStatus","sync.getBlocks","sync.getBlob","sync.listRepos","sync.listBlobs","sync.getRecord","repo.describeRepo","repo.getRecord","repo.listRecords","repo.createRecord","repo.deleteRecord","repo.uploadBlob","repo.applyWrites","repo.putRecord","repo.importRepo","repo.listMissingBlobs","server.describeServer","identity.requestPlcOperationSignature","identity.signPlcOperation","identity.getMigrationToken","server.createSession","server.refreshSession","server.getSession","server.deleteSession","server.checkAccountStatus","server.activateAccount","server.deactivateAccount","server.resetMigration","server.requestEmailUpdate","server.requestEmailConfirmation","server.updateEmail","server.getServiceAuth","passkey.initPasskeyRegistration","passkey.getRegistrationOptions","passkey.completePasskeyRegistration","passkey.listPasskeys","passkey.deletePasskey"],"sources":["../src/cbor-compat.ts","../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/passkey.ts","../src/oauth.ts","../src/middleware/auth.ts","../src/did-resolver.ts","../src/did-cache.ts","../src/xrpc-proxy.ts","../src/format.ts","../src/xrpc/sync.ts","../src/validation.ts","../src/xrpc/repo.ts","../src/xrpc/server.ts","../src/migration-token.ts","../src/xrpc/identity.ts","../src/passkey-ui.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * CBOR compatibility layer for migrating from @atproto/lex-cbor to @atcute/cbor.\n *\n * @atcute/cbor uses lazy wrappers (BytesWrapper, CidLinkWrapper) that are\n * compatible with atproto's lex-json format. This layer handles conversion\n * of @atproto CID objects to CidLinkWrapper for encoding.\n *\n * Use toCidLink/fromCidLink to convert between lex-json and raw CID types.\n */\nimport {\n\tencode as atcuteEncode,\n\tdecode as atcuteDecode,\n\ttoCidLink,\n\ttoBytes,\n\tfromBytes,\n\tisBytes,\n\ttype CidLink,\n} from \"@atcute/cbor\";\nimport { fromString } from \"@atcute/cid\";\nimport type { CID } from \"@atproto/lex-data\";\n\n/**\n * Check if a value is an @atproto CID object.\n */\nfunction isAtprotoCid(value: unknown): value is CID {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\tconst obj = value as Record<string | symbol, unknown>;\n\treturn \"asCID\" in obj && obj[Symbol.toStringTag] === \"CID\";\n}\n\n/**\n * Convert @atproto CID to @atcute CidLink.\n */\nfunction atprotoCidToCidLink(cid: CID): CidLink {\n\treturn toCidLink(fromString(cid.toString()));\n}\n\n/**\n * Recursively convert @atproto CIDs to @atcute CidLinks for encoding.\n */\nfunction convertCidsForEncode(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn value;\n\t}\n\n\tif (typeof value !== \"object\") {\n\t\treturn value;\n\t}\n\n\t// Handle Uint8Array - wrap with toBytes() for @atcute/cbor\n\tif (ArrayBuffer.isView(value) && value instanceof Uint8Array) {\n\t\treturn toBytes(value);\n\t}\n\n\t// Convert @atproto CID to @atcute CidLink\n\tif (isAtprotoCid(value)) {\n\t\treturn atprotoCidToCidLink(value);\n\t}\n\n\t// Handle arrays\n\tif (Array.isArray(value)) {\n\t\treturn value.map(convertCidsForEncode);\n\t}\n\n\t// Handle plain objects\n\tconst obj = value as object;\n\tif (obj.constructor === Object) {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, val] of Object.entries(obj)) {\n\t\t\tresult[key] = convertCidsForEncode(val);\n\t\t}\n\t\treturn result;\n\t}\n\n\treturn value;\n}\n\n/**\n * Encode a value to CBOR, automatically converting @atproto CIDs to CidLinks.\n *\n * Decoded values will contain CidLinkWrapper objects which have a lazy $link\n * getter returning the CID string - compatible with lex-json format.\n */\nexport function encode(value: unknown): Uint8Array {\n\tconst converted = convertCidsForEncode(value);\n\treturn atcuteEncode(converted);\n}\n\n/**\n * Recursively convert @atcute wrappers back to raw types for decoding.\n */\nfunction convertWrappersForDecode(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn value;\n\t}\n\n\tif (typeof value !== \"object\") {\n\t\treturn value;\n\t}\n\n\t// Unwrap BytesWrapper to raw Uint8Array\n\tif (isBytes(value)) {\n\t\treturn fromBytes(value);\n\t}\n\n\t// CidLinkWrapper is left as-is since it has $link getter for lex-json compat\n\n\t// Handle arrays\n\tif (Array.isArray(value)) {\n\t\treturn value.map(convertWrappersForDecode);\n\t}\n\n\t// Handle plain objects\n\tconst obj = value as object;\n\tif (obj.constructor === Object) {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, val] of Object.entries(obj)) {\n\t\t\tresult[key] = convertWrappersForDecode(val);\n\t\t}\n\t\treturn result;\n\t}\n\n\treturn value;\n}\n\n/**\n * Decode CBOR bytes.\n *\n * Unwraps BytesWrapper to raw Uint8Array for compatibility.\n * CidLinkWrapper is left as-is (access via .$link for lex-json compat).\n */\nexport function decode(bytes: Uint8Array): unknown {\n\tconst decoded = atcuteDecode(bytes);\n\treturn convertWrappersForDecode(decoded);\n}\n","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\temail TEXT\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\n\t\t\t-- Collection name cache (for describeRepo)\n\t\t\tCREATE TABLE IF NOT EXISTS collections (\n\t\t\t\tcollection TEXT PRIMARY KEY\n\t\t\t);\n\n\t\t\t-- Passkey credentials (WebAuthn)\n\t\t\tCREATE TABLE IF NOT EXISTS passkeys (\n\t\t\t\tcredential_id TEXT PRIMARY KEY,\n\t\t\t\tpublic_key BLOB NOT NULL,\n\t\t\t\tcounter INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tname TEXT,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tlast_used_at TEXT\n\t\t\t);\n\n\t\t\t-- Passkey registration tokens (short-lived, 10 min TTL)\n\t\t\tCREATE TABLE IF NOT EXISTS passkey_tokens (\n\t\t\t\ttoken TEXT PRIMARY KEY,\n\t\t\t\tchallenge TEXT NOT NULL,\n\t\t\t\texpires_at INTEGER NOT NULL,\n\t\t\t\tname TEXT\n\t\t\t);\n\t\t`);\n\n\t\t// Migration: add email column for existing databases\n\t\ttry {\n\t\t\tthis.sql.exec(\"ALTER TABLE repo_state ADD COLUMN email TEXT\");\n\t\t} catch {\n\t\t\t// Column already exists\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 * Get the stored email address.\n\t */\n\tgetEmail(): string | null {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT email FROM repo_state WHERE id = 1\")\n\t\t\t.toArray();\n\t\treturn rows.length > 0 ? ((rows[0]!.email as string) ?? null) : null;\n\t}\n\n\t/**\n\t * Set the email address.\n\t */\n\tsetEmail(email: string): void {\n\t\tthis.sql.exec(\"UPDATE repo_state SET email = ? WHERE id = 1\", email);\n\t}\n\n\t// ============================================\n\t// Collection Cache Methods\n\t// ============================================\n\n\t/**\n\t * Get all cached collection names.\n\t */\n\tgetCollections(): string[] {\n\t\tconst rows = this.sql\n\t\t\t.exec(\"SELECT collection FROM collections ORDER BY collection\")\n\t\t\t.toArray();\n\t\treturn rows.map((row) => row.collection as string);\n\t}\n\n\t/**\n\t * Add a collection name to the cache (no-op if already present).\n\t */\n\taddCollection(collection: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT OR IGNORE INTO collections (collection) VALUES (?)\",\n\t\t\tcollection,\n\t\t);\n\t}\n\n\t/**\n\t * Check if the collections cache has been populated.\n\t */\n\thasCollections(): boolean {\n\t\tconst rows = this.sql.exec(\"SELECT 1 FROM collections LIMIT 1\").toArray();\n\t\treturn rows.length > 0;\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\t// ============================================\n\t// Passkey Methods\n\t// ============================================\n\n\t/**\n\t * Save a passkey credential.\n\t */\n\tsavePasskey(\n\t\tcredentialId: string,\n\t\tpublicKey: Uint8Array,\n\t\tcounter: number,\n\t\tname?: string,\n\t): void {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO passkeys (credential_id, public_key, counter, name)\n\t\t\t VALUES (?, ?, ?, ?)`,\n\t\t\tcredentialId,\n\t\t\tpublicKey,\n\t\t\tcounter,\n\t\t\tname ?? null,\n\t\t);\n\t}\n\n\t/**\n\t * Get a passkey by credential ID.\n\t */\n\tgetPasskey(credentialId: string): {\n\t\tcredentialId: string;\n\t\tpublicKey: Uint8Array;\n\t\tcounter: number;\n\t\tname: string | null;\n\t\tcreatedAt: string;\n\t\tlastUsedAt: string | null;\n\t} | null {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT credential_id, public_key, counter, name, created_at, last_used_at\n\t\t\t\t FROM passkeys WHERE credential_id = ?`,\n\t\t\t\tcredentialId,\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\tcredentialId: row.credential_id as string,\n\t\t\tpublicKey: new Uint8Array(row.public_key as ArrayBuffer),\n\t\t\tcounter: row.counter as number,\n\t\t\tname: row.name as string | null,\n\t\t\tcreatedAt: row.created_at as string,\n\t\t\tlastUsedAt: row.last_used_at as string | null,\n\t\t};\n\t}\n\n\t/**\n\t * List all passkeys.\n\t */\n\tlistPasskeys(): Array<{\n\t\tcredentialId: string;\n\t\tname: string | null;\n\t\tcreatedAt: string;\n\t\tlastUsedAt: string | null;\n\t}> {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT credential_id, name, created_at, last_used_at\n\t\t\t\t FROM passkeys ORDER BY created_at DESC`,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\treturn rows.map((row) => ({\n\t\t\tcredentialId: row.credential_id as string,\n\t\t\tname: row.name as string | null,\n\t\t\tcreatedAt: row.created_at as string,\n\t\t\tlastUsedAt: row.last_used_at as string | null,\n\t\t}));\n\t}\n\n\t/**\n\t * Delete a passkey.\n\t */\n\tdeletePasskey(credentialId: string): boolean {\n\t\tconst before = this.sql.exec(\"SELECT COUNT(*) as c FROM passkeys\").one();\n\t\tthis.sql.exec(\"DELETE FROM passkeys WHERE credential_id = ?\", credentialId);\n\t\tconst after = this.sql.exec(\"SELECT COUNT(*) as c FROM passkeys\").one();\n\t\treturn (before.c as number) > (after.c as number);\n\t}\n\n\t/**\n\t * Update passkey counter after successful authentication.\n\t */\n\tupdatePasskeyCounter(credentialId: string, counter: number): void {\n\t\tthis.sql.exec(\n\t\t\t`UPDATE passkeys SET counter = ?, last_used_at = datetime('now')\n\t\t\t WHERE credential_id = ?`,\n\t\t\tcounter,\n\t\t\tcredentialId,\n\t\t);\n\t}\n\n\t/**\n\t * Check if any passkeys exist (for conditional UI).\n\t */\n\thasPasskeys(): boolean {\n\t\tconst result = this.sql.exec(\"SELECT COUNT(*) as c FROM passkeys\").one();\n\t\treturn (result.c as number) > 0;\n\t}\n\n\t// ============================================\n\t// Passkey Registration Token Methods\n\t// ============================================\n\n\t/**\n\t * Save a registration token with challenge and optional name.\n\t */\n\tsavePasskeyToken(\n\t\ttoken: string,\n\t\tchallenge: string,\n\t\texpiresAt: number,\n\t\tname?: string,\n\t): void {\n\t\tthis.sql.exec(\n\t\t\t`INSERT INTO passkey_tokens (token, challenge, expires_at, name) VALUES (?, ?, ?, ?)`,\n\t\t\ttoken,\n\t\t\tchallenge,\n\t\t\texpiresAt,\n\t\t\tname ?? null,\n\t\t);\n\t}\n\n\t/**\n\t * Get and consume a registration token.\n\t */\n\tconsumePasskeyToken(\n\t\ttoken: string,\n\t): { challenge: string; name: string | null } | null {\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t`SELECT challenge, expires_at, name FROM passkey_tokens WHERE token = ?`,\n\t\t\t\ttoken,\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\t// Delete the token (single-use)\n\t\tthis.sql.exec(\"DELETE FROM passkey_tokens WHERE token = ?\", token);\n\n\t\t// Check if expired\n\t\tif (Date.now() > expiresAt) return null;\n\n\t\treturn {\n\t\t\tchallenge: row.challenge as string,\n\t\t\tname: (row.name as string) ?? null,\n\t\t};\n\t}\n\n\t/**\n\t * Clean up expired tokens.\n\t */\n\tcleanupPasskeyTokens(): void {\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM passkey_tokens WHERE expires_at < ?\",\n\t\t\tDate.now(),\n\t\t);\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\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\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\ttoken_endpoint_auth_method TEXT NOT NULL DEFAULT 'none',\n\t\t\t\tjwks TEXT,\n\t\t\t\tjwks_uri TEXT,\n\t\t\t\tcached_at INTEGER NOT NULL\n\t\t\t);\n\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\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\n\t\t\tCREATE TABLE IF NOT EXISTS oauth_webauthn_challenges (\n\t\t\t\tchallenge 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_challenges_created ON oauth_webauthn_challenges(created_at);\n\t\t`);\n\n\t\t// Migration: add columns for client auth metadata if missing\n\t\tthis.migrateClientTable();\n\t}\n\n\tprivate migrateClientTable(): void {\n\t\tconst columns = this.sql\n\t\t\t.exec(\"PRAGMA table_info(oauth_clients)\")\n\t\t\t.toArray()\n\t\t\t.map((r) => r.name as string);\n\n\t\tif (!columns.includes(\"token_endpoint_auth_method\")) {\n\t\t\tthis.sql.exec(\n\t\t\t\t\"ALTER TABLE oauth_clients ADD COLUMN token_endpoint_auth_method TEXT NOT NULL DEFAULT 'none'\",\n\t\t\t);\n\t\t\tthis.sql.exec(\"ALTER TABLE oauth_clients ADD COLUMN jwks TEXT\");\n\t\t\tthis.sql.exec(\"ALTER TABLE oauth_clients ADD COLUMN jwks_uri TEXT\");\n\t\t\tthis.sql.exec(\"DELETE FROM oauth_clients\");\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\t// WebAuthn challenges expire after 2 minutes\n\t\tconst challengeExpiry = now - 2 * 60 * 1000;\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_webauthn_challenges WHERE created_at < ?\",\n\t\t\tchallengeExpiry,\n\t\t);\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, token_endpoint_auth_method, jwks, jwks_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.tokenEndpointAuthMethod ?? \"none\",\n\t\t\tmetadata.jwks ? JSON.stringify(metadata.jwks) : null,\n\t\t\tmetadata.jwksUri ?? 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, token_endpoint_auth_method, jwks, jwks_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\ttokenEndpointAuthMethod:\n\t\t\t\t(row.token_endpoint_auth_method as \"none\" | \"private_key_jwt\") ??\n\t\t\t\t\"none\",\n\t\t\tjwks: row.jwks\n\t\t\t\t? (JSON.parse(row.jwks as string) as ClientMetadata[\"jwks\"])\n\t\t\t\t: undefined,\n\t\t\tjwksUri: (row.jwks_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\tthis.sql.exec(\"DELETE FROM oauth_webauthn_challenges\");\n\t}\n\n\t// ============================================\n\t// WebAuthn Challenges\n\t// ============================================\n\n\t/**\n\t * Save a WebAuthn challenge for later verification\n\t */\n\tsaveWebAuthnChallenge(challenge: string): void {\n\t\tthis.sql.exec(\n\t\t\t\"INSERT INTO oauth_webauthn_challenges (challenge, created_at) VALUES (?, ?)\",\n\t\t\tchallenge,\n\t\t\tDate.now(),\n\t\t);\n\t}\n\n\t/**\n\t * Consume a WebAuthn challenge (single-use, deleted after retrieval)\n\t * @returns true if challenge was valid and consumed, false if not found or expired\n\t */\n\tconsumeWebAuthnChallenge(challenge: string): boolean {\n\t\t// Check if challenge exists and is not expired (2 min TTL)\n\t\tconst twoMinutesAgo = Date.now() - 2 * 60 * 1000;\n\t\tconst rows = this.sql\n\t\t\t.exec(\n\t\t\t\t\"SELECT challenge FROM oauth_webauthn_challenges WHERE challenge = ? AND created_at > ?\",\n\t\t\t\tchallenge,\n\t\t\t\ttwoMinutesAgo,\n\t\t\t)\n\t\t\t.toArray();\n\n\t\tif (rows.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Delete the challenge (single-use)\n\t\tthis.sql.exec(\n\t\t\t\"DELETE FROM oauth_webauthn_challenges WHERE challenge = ?\",\n\t\t\tchallenge,\n\t\t);\n\n\t\treturn true;\n\t}\n}\n","import { encode as cborEncode, decode as cborDecode } from \"./cbor-compat\";\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 * Identity event payload for the firehose\n */\nexport interface IdentityEvent {\n\tseq: number;\n\tdid: string;\n\thandle: string;\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 commit event wrapper\n */\nexport interface SeqCommitEvent {\n\tseq: number;\n\ttype: \"commit\";\n\tevent: CommitEvent;\n\ttime: string;\n}\n\n/**\n * Sequenced identity event wrapper\n */\nexport interface SeqIdentityEvent {\n\tseq: number;\n\ttype: \"identity\";\n\tevent: IdentityEvent;\n\ttime: string;\n}\n\n/**\n * Sequenced event (commit or identity)\n */\nexport type SeqEvent = SeqCommitEvent | SeqIdentityEvent;\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);\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 * Skips identity events that have empty payloads.\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\tconst events: SeqEvent[] = [];\n\n\t\tfor (const row of rows) {\n\t\t\tconst eventType = row.event_type as string;\n\t\t\tconst payload = new Uint8Array(row.payload as ArrayBuffer);\n\t\t\tconst seq = row.seq as number;\n\t\t\tconst time = row.created_at as string;\n\n\t\t\tif (eventType === \"identity\") {\n\t\t\t\t// Skip legacy identity events with empty payload\n\t\t\t\tif (payload.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Decode identity event with proper payload\n\t\t\t\tconst decoded = cborDecode(payload) as Omit<IdentityEvent, \"seq\">;\n\t\t\t\tevents.push({\n\t\t\t\t\tseq,\n\t\t\t\t\ttype: \"identity\",\n\t\t\t\t\tevent: { ...decoded, seq },\n\t\t\t\t\ttime,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t// Commit event\n\t\t\t\tconst decoded = cborDecode(payload) as Omit<CommitEvent, \"seq\">;\n\t\t\t\tevents.push({\n\t\t\t\t\tseq,\n\t\t\t\t\ttype: \"commit\",\n\t\t\t\t\tevent: { ...decoded, seq },\n\t\t\t\t\ttime,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn events;\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 {\n\tcreate as createCid,\n\tCODEC_RAW,\n\ttoString as cidToString,\n} from \"@atcute/cid\";\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 cidObj = await createCid(CODEC_RAW, bytes);\n\t\tconst cidStr = cidToString(cidObj);\n\n\t\t// Store in R2 with DID prefix for isolation\n\t\tconst key = `${this.did}/${cidStr}`;\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: cidStr },\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 string.\n\t */\n\tasync getBlob(cid: string): Promise<R2ObjectBody | null> {\n\t\tconst key = `${this.did}/${cid}`;\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: string): Promise<boolean> {\n\t\tconst key = `${this.did}/${cid}`;\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\twriteCarStream,\n\treadCarWithRoot,\n\tgetRecords,\n\ttype CarBlock,\n\ttype RecordCreateOp,\n\ttype RecordUpdateOp,\n\ttype RecordDeleteOp,\n\ttype RecordWriteOp,\n} from \"@atproto/repo\";\n/** Record type compatible with @atproto/repo operations */\ntype RepoRecord = Record<string, unknown>;\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { CID, asCid, isBlobRef } from \"@atproto/lex-data\";\nimport { now as tidNow } from \"@atcute/tid\";\nimport { encode as cborEncode } from \"./cbor-compat\";\nimport { SqliteRepoStorage } from \"./storage\";\nimport { SqliteOAuthStorage } from \"./oauth-storage\";\nimport {\n\tSequencer,\n\ttype SeqEvent,\n\ttype SeqCommitEvent,\n\ttype SeqIdentityEvent,\n\ttype CommitData,\n} from \"./sequencer\";\nimport { BlobStore, type BlobRef } from \"./blobs\";\nimport { jsonToLex } from \"@atproto/lex-json\";\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\n\t\t\t\t// Run cleanup on initialization\n\t\t\t\tthis.runCleanup();\n\n\t\t\t\t// Schedule periodic cleanup (run every 24 hours)\n\t\t\t\tconst currentAlarm = await this.ctx.storage.getAlarm();\n\t\t\t\tif (currentAlarm === null) {\n\t\t\t\t\tawait this.ctx.storage.setAlarm(Date.now() + 86400000); // 24 hours\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Run cleanup on storage to remove expired entries\n\t */\n\tprivate runCleanup(): void {\n\t\tif (this.storage) {\n\t\t\tthis.storage.cleanupPasskeyTokens();\n\t\t}\n\t\tif (this.oauthStorage) {\n\t\t\tthis.oauthStorage.cleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Alarm handler for periodic cleanup\n\t * Called by Cloudflare Workers when the alarm fires\n\t */\n\toverride async alarm(): Promise<void> {\n\t\tawait this.ensureStorageInitialized();\n\n\t\t// Run cleanup\n\t\tthis.runCleanup();\n\n\t\t// Schedule next cleanup in 24 hours\n\t\tawait this.ctx.storage.setAlarm(Date.now() + 86400000);\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 storage = await this.getStorage();\n\n\t\t// Lazy backfill: if the cache is empty and the repo has content, populate it\n\t\tif (!storage.hasCollections() && (await storage.getRoot())) {\n\t\t\tconst seen = new Set<string>();\n\t\t\tfor await (const record of repo.walkRecords()) {\n\t\t\t\tif (!seen.has(record.collection)) {\n\t\t\t\t\tseen.add(record.collection);\n\t\t\t\t\tstorage.addCollection(record.collection);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tdid: repo.did,\n\t\t\tcollections: storage.getCollections(),\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: `at://${repo.did}/${record.collection}/${record.rkey}`,\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 || tidNow();\n\t\tconst createOp: RecordCreateOp = {\n\t\t\taction: WriteOpAction.Create,\n\t\t\tcollection,\n\t\t\trkey: actualRkey,\n\t\t\trecord: jsonToLex(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// Update collections cache\n\t\tthis.storage!.addCollection(collection);\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: `at://${this.repo.did}/${collection}/${actualRkey}`,\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 normalizedRecord = jsonToLex(record) as RepoRecord;\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: normalizedRecord,\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: normalizedRecord,\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// Update collections cache\n\t\tthis.storage!.addCollection(collection);\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: `at://${this.repo.did}/${collection}/${rkey}`,\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 || tidNow();\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: jsonToLex(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: jsonToLex(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// Update collections cache for create/update ops\n\t\tfor (const op of ops) {\n\t\t\tif (op.action !== WriteOpAction.Delete) {\n\t\t\t\tthis.storage!.addCollection(op.collection);\n\t\t\t}\n\t\t}\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: `at://${this.repo.did}/${result.collection}/${result.rkey}`,\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 * Handle streaming getRepo via fetch (not RPC, to enable streaming response).\n\t */\n\tprivate async handleGetRepo(): Promise<Response> {\n\t\tconst storage = await this.getStorage();\n\t\tconst root = await storage.getRoot();\n\n\t\tif (!root) {\n\t\t\treturn Response.json(\n\t\t\t\t{ error: \"RepoNotFound\", message: \"No repository root found\" },\n\t\t\t\t{ status: 404 },\n\t\t\t);\n\t\t}\n\n\t\t// Lazily iterate SQLite rows — the cursor is already lazy,\n\t\t// only .toArray() would materialize everything in memory.\n\t\tconst cursor = this.ctx.storage.sql.exec(\n\t\t\t\"SELECT cid, bytes FROM blocks\",\n\t\t);\n\n\t\tasync function* blocks(): AsyncGenerator<CarBlock> {\n\t\t\tfor (const row of cursor) {\n\t\t\t\tyield {\n\t\t\t\t\tcid: CID.parse(row.cid as string),\n\t\t\t\t\tbytes: new Uint8Array(row.bytes as ArrayBuffer),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst carIter = writeCarStream(root, blocks())[Symbol.asyncIterator]();\n\n\t\tconst stream = new ReadableStream<Uint8Array>({\n\t\t\tasync pull(controller) {\n\t\t\t\tconst { value, done } = await carIter.next();\n\t\t\t\tif (done) {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.enqueue(value);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { \"Content-Type\": \"application/vnd.ipld.car\" },\n\t\t});\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: Get record with proof as CAR file.\n\t * Returns the commit block and all MST blocks needed to verify\n\t * the existence (or non-existence) of a record.\n\t * Used by com.atproto.sync.getRecord for record verification.\n\t */\n\tasync rpcGetRecordProof(\n\t\tcollection: string,\n\t\trkey: string,\n\t): 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// Use @atproto/repo's getRecords to generate the proof CAR\n\t\t// This returns an async iterable of CAR chunks\n\t\tconst carChunks: Uint8Array[] = [];\n\t\tfor await (const chunk of getRecords(storage, root, [\n\t\t\t{ collection, rkey },\n\t\t])) {\n\t\t\tcarChunks.push(chunk);\n\t\t}\n\n\t\t// Concatenate all chunks into a single Uint8Array\n\t\tconst totalLength = carChunks.reduce((acc, chunk) => acc + chunk.length, 0);\n\t\tconst result = new Uint8Array(totalLength);\n\t\tlet offset = 0;\n\t\tfor (const chunk of carChunks) {\n\t\t\tresult.set(chunk, offset);\n\t\t\toffset += chunk.length;\n\t\t}\n\n\t\treturn result;\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 = tidNow();\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 and collection names from all imported records\n\t\tconst seenCollections = new Set<string>();\n\t\tfor await (const record of this.repo.walkRecords()) {\n\t\t\tif (!seenCollections.has(record.collection)) {\n\t\t\t\tseenCollections.add(record.collection);\n\t\t\t\tthis.storage!.addCollection(record.collection);\n\t\t\t}\n\t\t\tconst blobCids = extractBlobCids(record.record);\n\t\t\tif (blobCids.length > 0) {\n\t\t\t\tconst uri = `at://${this.repo.did}/${record.collection}/${record.rkey}`;\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 (60MB)\n\t\tconst MAX_BLOB_SIZE = 60 * 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\t\treturn this.blobStore.getBlob(cidStr);\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: SeqCommitEvent): 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 identity event frame.\n\t */\n\tprivate encodeIdentityFrame(event: SeqIdentityEvent): Uint8Array {\n\t\tconst header = { op: 1, t: \"#identity\" };\n\t\treturn this.encodeFrame(header, event.event);\n\t}\n\n\t/**\n\t * Encode any event frame based on its type.\n\t */\n\tprivate encodeEventFrame(event: SeqEvent): Uint8Array {\n\t\tif (event.type === \"identity\") {\n\t\t\treturn this.encodeIdentityFrame(event);\n\t\t}\n\t\treturn this.encodeCommitFrame(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.encodeEventFrame(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.encodeEventFrame(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 stored email\n\t */\n\tasync rpcGetEmail(): Promise<{ email: string | null }> {\n\t\tconst storage = await this.getStorage();\n\t\treturn { email: storage.getEmail() };\n\t}\n\n\t/**\n\t * RPC method: Update stored email\n\t */\n\tasync rpcUpdateEmail(email: string): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.setEmail(email);\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<{\n\t\tblobs: Array<{ cid: string; recordUri: string }>;\n\t\tcursor?: string;\n\t}> {\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(header);\n\t\tconst bodyBytes = cborEncode(body);\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// Health Check RPC Methods\n\t// ============================================\n\n\t/**\n\t * RPC method: Health check - verifies storage is accessible\n\t */\n\tasync rpcHealthCheck(): Promise<{ ok: true }> {\n\t\tthis.ctx.storage.sql.exec(\"SELECT 1\").toArray();\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * RPC method: Firehose status - returns subscriber count and latest sequence\n\t */\n\tasync rpcGetFirehoseStatus(): Promise<{\n\t\tsubscribers: number;\n\t\tlatestSeq: number | null;\n\t}> {\n\t\tconst sockets = this.ctx.getWebSockets();\n\t\tawait this.ensureStorageInitialized();\n\t\tconst storage = await this.getStorage();\n\t\tconst seq = await storage.getSeq();\n\t\treturn {\n\t\t\tsubscribers: sockets.length,\n\t\t\tlatestSeq: seq || null,\n\t\t};\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// Passkey RPC Methods\n\t// ============================================\n\n\t/** Save a passkey credential */\n\tasync rpcSavePasskey(\n\t\tcredentialId: string,\n\t\tpublicKey: Uint8Array,\n\t\tcounter: number,\n\t\tname?: string,\n\t): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.savePasskey(credentialId, publicKey, counter, name);\n\t}\n\n\t/** Get a passkey by credential ID */\n\tasync rpcGetPasskey(credentialId: string): Promise<{\n\t\tcredentialId: string;\n\t\tpublicKey: Uint8Array;\n\t\tcounter: number;\n\t\tname: string | null;\n\t\tcreatedAt: string;\n\t\tlastUsedAt: string | null;\n\t} | null> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.getPasskey(credentialId);\n\t}\n\n\t/** List all passkeys */\n\tasync rpcListPasskeys(): Promise<\n\t\tArray<{\n\t\t\tcredentialId: string;\n\t\t\tname: string | null;\n\t\t\tcreatedAt: string;\n\t\t\tlastUsedAt: string | null;\n\t\t}>\n\t> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.listPasskeys();\n\t}\n\n\t/** Delete a passkey */\n\tasync rpcDeletePasskey(credentialId: string): Promise<boolean> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.deletePasskey(credentialId);\n\t}\n\n\t/** Update passkey counter after authentication */\n\tasync rpcUpdatePasskeyCounter(\n\t\tcredentialId: string,\n\t\tcounter: number,\n\t): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.updatePasskeyCounter(credentialId, counter);\n\t}\n\n\t/** Check if passkeys exist */\n\tasync rpcHasPasskeys(): Promise<boolean> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.hasPasskeys();\n\t}\n\n\t/** Save a registration token */\n\tasync rpcSavePasskeyToken(\n\t\ttoken: string,\n\t\tchallenge: string,\n\t\texpiresAt: number,\n\t\tname?: string,\n\t): Promise<void> {\n\t\tconst storage = await this.getStorage();\n\t\tstorage.savePasskeyToken(token, challenge, expiresAt, name);\n\t}\n\n\t/** Consume a registration token */\n\tasync rpcConsumePasskeyToken(\n\t\ttoken: string,\n\t): Promise<{ challenge: string; name: string | null } | null> {\n\t\tconst storage = await this.getStorage();\n\t\treturn storage.consumePasskeyToken(token);\n\t}\n\n\t/** Save a WebAuthn challenge for passkey authentication */\n\tasync rpcSaveWebAuthnChallenge(challenge: string): Promise<void> {\n\t\tconst oauthStorage = await this.getOAuthStorage();\n\t\toauthStorage.saveWebAuthnChallenge(challenge);\n\t}\n\n\t/** Consume a WebAuthn challenge (single-use) */\n\tasync rpcConsumeWebAuthnChallenge(challenge: string): Promise<boolean> {\n\t\tconst oauthStorage = await this.getOAuthStorage();\n\t\treturn oauthStorage.consumeWebAuthnChallenge(challenge);\n\t}\n\n\t/**\n\t * HTTP fetch handler for WebSocket upgrades and streaming responses.\n\t * Used instead of RPC when the response can't be serialized (WebSocket)\n\t * or when streaming is needed to avoid buffering large payloads (getRepo).\n\t */\n\toverride async fetch(request: Request): Promise<Response> {\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\t\tif (url.pathname === \"/xrpc/com.atproto.sync.getRepo\") {\n\t\t\treturn this.handleGetRepo();\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\t// Convert Uint8Array to { $bytes: \"<base64>\" }\n\tif (obj instanceof Uint8Array) {\n\t\tlet binary = \"\";\n\t\tfor (let i = 0; i < obj.length; i++) {\n\t\t\tbinary += String.fromCharCode(obj[i]!);\n\t\t}\n\t\treturn { $bytes: btoa(binary) };\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;\n\n// Service JWTs for external services (like video.bsky.app) need longer expiry\n// because video processing can take several minutes before the callback arrives.\nconst SERVICE_JWT_EXPIRY_SECONDS = 5 * MINUTE;\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 + SERVICE_JWT_EXPIRY_SECONDS;\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, errors, type JWTPayload } from \"jose\";\nimport { compare } from \"bcryptjs\";\n\n/**\n * Error thrown when a JWT has expired.\n * Callers should return HTTP 400 with error code 'ExpiredToken'.\n */\nexport class TokenExpiredError extends Error {\n\tconstructor(message = \"Token has expired\") {\n\t\tsuper(message);\n\t\tthis.name = \"TokenExpiredError\";\n\t}\n}\n\n// Match official PDS: 120 minutes for session access tokens\nconst ACCESS_TOKEN_LIFETIME = \"120m\";\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: \"com.atproto.access\" })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"at+jwt\" })\n\t\t.setIssuedAt()\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\" })\n\t\t.setProtectedHeader({ alg: \"HS256\", typ: \"refresh+jwt\" })\n\t\t.setIssuedAt()\n\t\t.setAudience(serviceDid)\n\t\t.setSubject(userDid)\n\t\t.setJti(jti)\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 * Throws TokenExpiredError if the token has expired.\n */\nexport async function verifyAccessToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tlet payload: JWTPayload;\n\tlet protectedHeader: { typ?: string };\n\n\ttry {\n\t\tconst result = await jwtVerify(token, secret, {\n\t\t\taudience: serviceDid,\n\t\t});\n\t\tpayload = result.payload;\n\t\tprotectedHeader = result.protectedHeader;\n\t} catch (err) {\n\t\tif (err instanceof errors.JWTExpired) {\n\t\t\tthrow new TokenExpiredError();\n\t\t}\n\t\tthrow err;\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 !== \"com.atproto.access\") {\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 * Throws TokenExpiredError if the token has expired.\n */\nexport async function verifyRefreshToken(\n\ttoken: string,\n\tjwtSecret: string,\n\tserviceDid: string,\n): Promise<JWTPayload> {\n\tconst secret = createSecretKey(jwtSecret);\n\n\tlet payload: JWTPayload;\n\tlet protectedHeader: { typ?: string };\n\n\ttry {\n\t\tconst result = await jwtVerify(token, secret, {\n\t\t\taudience: serviceDid,\n\t\t});\n\t\tpayload = result.payload;\n\t\tprotectedHeader = result.protectedHeader;\n\t} catch (err) {\n\t\tif (err instanceof errors.JWTExpired) {\n\t\t\tthrow new TokenExpiredError();\n\t\t}\n\t\tthrow err;\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 * Passkey (WebAuthn) support for Cirrus PDS\n *\n * Handles passkey registration and authentication using the @simplewebauthn/server library.\n * Uses a CLI-driven flow where:\n * 1. CLI generates a registration token\n * 2. User opens the URL on their device\n * 3. Device performs the WebAuthn ceremony\n * 4. Passkey is stored in the Durable Object\n */\n\nimport {\n\tgenerateRegistrationOptions,\n\tverifyRegistrationResponse,\n\tgenerateAuthenticationOptions,\n\tverifyAuthenticationResponse,\n} from \"@simplewebauthn/server\";\nimport type {\n\tAuthenticationResponseJSON,\n\tRegistrationResponseJSON,\n} from \"@simplewebauthn/server\";\nimport type { AccountDurableObject } from \"./account-do\";\n\n// Re-export for use by other modules (e.g., oauth.ts)\nexport type { AuthenticationResponseJSON, RegistrationResponseJSON };\n\n/** Options for creating a new credential */\nexport interface PublicKeyCredentialCreationOptionsJSON {\n\trp: { name: string; id?: string };\n\tuser: { id: string; name: string; displayName: string };\n\tchallenge: string;\n\tpubKeyCredParams: Array<{ alg: number; type: string }>;\n\ttimeout?: number;\n\tattestation?: string;\n\tauthenticatorSelection?: {\n\t\tauthenticatorAttachment?: string;\n\t\trequireResidentKey?: boolean;\n\t\tresidentKey?: string;\n\t\tuserVerification?: string;\n\t};\n\texcludeCredentials?: Array<{\n\t\tid: string;\n\t\ttype?: string;\n\t\ttransports?: string[];\n\t}>;\n}\n\n/** Options for authenticating with an existing credential */\ninterface PublicKeyCredentialRequestOptionsJSON {\n\tchallenge: string;\n\ttimeout?: number;\n\trpId?: string;\n\tallowCredentials?: Array<{\n\t\tid: string;\n\t\ttype?: string;\n\t\ttransports?: string[];\n\t}>;\n\tuserVerification?: string;\n}\n\n/** Token TTL in milliseconds (10 minutes) */\nconst TOKEN_TTL_MS = 10 * 60 * 1000;\n\n/**\n * Generate a secure random token\n */\nfunction generateToken(): string {\n\tconst bytes = new Uint8Array(32);\n\tcrypto.getRandomValues(bytes);\n\t// Use base64url encoding (URL-safe)\n\treturn btoa(String.fromCharCode(...bytes))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=/g, \"\");\n}\n\nexport interface PasskeyRegistrationInit {\n\t/** Token to include in the registration URL */\n\ttoken: string;\n\t/** Full URL for the user to visit */\n\turl: string;\n\t/** When this token expires */\n\texpiresAt: number;\n}\n\nexport interface PasskeyInfo {\n\t/** Credential ID (base64url encoded) */\n\tid: string;\n\t/** User-provided name for this passkey */\n\tname: string | null;\n\t/** When the passkey was created */\n\tcreatedAt: string;\n\t/** When the passkey was last used */\n\tlastUsedAt: string | null;\n}\n\n/**\n * Initialize passkey registration\n *\n * Generates a registration token and challenge, stores them,\n * and returns the URL for the user to visit.\n */\nexport async function initPasskeyRegistration(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\tdid: string,\n\tname?: string,\n): Promise<PasskeyRegistrationInit> {\n\tconst token = generateToken();\n\tconst expiresAt = Date.now() + TOKEN_TTL_MS;\n\n\t// Generate WebAuthn registration options\n\t// We need to generate a challenge here so it's available when the user visits the URL\n\tconst options = await generateRegistrationOptions({\n\t\trpName: \"Cirrus PDS\",\n\t\trpID: pdsHostname,\n\t\tuserName: did,\n\t\tuserDisplayName: name || did,\n\t\t// Require resident key (discoverable credential) for better UX\n\t\tauthenticatorSelection: {\n\t\t\tresidentKey: \"required\",\n\t\t\tuserVerification: \"preferred\",\n\t\t},\n\t\t// Don't require attestation (simpler, more compatible)\n\t\tattestationType: \"none\",\n\t});\n\n\t// Store the challenge with the token (and name for later)\n\tawait accountDO.rpcSavePasskeyToken(\n\t\ttoken,\n\t\toptions.challenge,\n\t\texpiresAt,\n\t\tname,\n\t);\n\n\tconst url = `https://${pdsHostname}/passkey/register?token=${token}`;\n\n\treturn {\n\t\ttoken,\n\t\turl,\n\t\texpiresAt,\n\t};\n}\n\n/**\n * Get registration options for a token\n *\n * Called when the user visits the registration URL.\n * Returns the WebAuthn options needed to start the ceremony.\n */\nexport async function getRegistrationOptions(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\tdid: string,\n\ttoken: string,\n): Promise<PublicKeyCredentialCreationOptionsJSON | null> {\n\t// Look up the token to get the challenge (but don't consume yet)\n\t// We need to re-consume on verification\n\tconst storage = await accountDO.rpcConsumePasskeyToken(token);\n\tif (!storage) {\n\t\treturn null;\n\t}\n\n\t// Re-save the token so it can be consumed during verification\n\t// (tokens are single-use, so we need to save it again)\n\tawait accountDO.rpcSavePasskeyToken(\n\t\ttoken,\n\t\tstorage.challenge,\n\t\tDate.now() + TOKEN_TTL_MS,\n\t\tstorage.name ?? undefined,\n\t);\n\n\t// Get existing passkeys to exclude them\n\tconst existingPasskeys = await accountDO.rpcListPasskeys();\n\n\t// Generate fresh options with the stored challenge\n\tconst options = await generateRegistrationOptions({\n\t\trpName: \"Cirrus PDS\",\n\t\trpID: pdsHostname,\n\t\tuserName: did,\n\t\tuserDisplayName: did,\n\t\tauthenticatorSelection: {\n\t\t\tresidentKey: \"required\",\n\t\t\tuserVerification: \"preferred\",\n\t\t},\n\t\tattestationType: \"none\",\n\t\texcludeCredentials: existingPasskeys.map((pk) => ({\n\t\t\tid: pk.credentialId,\n\t\t})),\n\t});\n\n\t// Override the challenge with our stored one\n\treturn {\n\t\t...options,\n\t\tchallenge: storage.challenge,\n\t};\n}\n\n/**\n * Complete passkey registration\n *\n * Verifies the registration response and stores the new passkey.\n * The name comes from the token (set during initPasskeyRegistration).\n */\nexport async function completePasskeyRegistration(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\ttoken: string,\n\tresponse: RegistrationResponseJSON,\n): Promise<{ success: true } | { success: false; error: string }> {\n\t// Consume the token to get the challenge and name\n\tconst tokenData = await accountDO.rpcConsumePasskeyToken(token);\n\tif (!tokenData) {\n\t\treturn { success: false, error: \"Invalid or expired token\" };\n\t}\n\n\ttry {\n\t\t// Verify the registration response\n\t\tconst verification = await verifyRegistrationResponse({\n\t\t\tresponse,\n\t\t\texpectedChallenge: tokenData.challenge,\n\t\t\texpectedOrigin: `https://${pdsHostname}`,\n\t\t\texpectedRPID: pdsHostname,\n\t\t});\n\n\t\tif (!verification.verified || !verification.registrationInfo) {\n\t\t\treturn { success: false, error: \"Verification failed\" };\n\t\t}\n\n\t\tconst { credential } = verification.registrationInfo;\n\n\t\t// Store the passkey (name comes from the token)\n\t\tawait accountDO.rpcSavePasskey(\n\t\t\tcredential.id,\n\t\t\tcredential.publicKey,\n\t\t\tcredential.counter,\n\t\t\ttokenData.name ?? undefined,\n\t\t);\n\n\t\treturn { success: true };\n\t} catch (err) {\n\t\tconsole.error(\"Passkey registration error:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: err instanceof Error ? err.message : \"Registration failed\",\n\t\t};\n\t}\n}\n\n/**\n * Generate authentication options for passkey login.\n * The challenge is stored server-side for later verification.\n */\nexport async function getAuthenticationOptions(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n): Promise<PublicKeyCredentialRequestOptionsJSON | null> {\n\t// Get all registered passkeys\n\tconst passkeys = await accountDO.rpcListPasskeys();\n\tif (passkeys.length === 0) {\n\t\treturn null;\n\t}\n\n\t// Explicitly list credential IDs - more compatible than discoverable credentials\n\tconst options = await generateAuthenticationOptions({\n\t\trpID: pdsHostname,\n\t\tuserVerification: \"preferred\",\n\t\tallowCredentials: passkeys.map((pk) => ({\n\t\t\tid: pk.credentialId,\n\t\t\t// Allow any transport type for maximum compatibility\n\t\t\ttransports: [\n\t\t\t\t\"internal\",\n\t\t\t\t\"hybrid\",\n\t\t\t\t\"usb\",\n\t\t\t\t\"ble\",\n\t\t\t\t\"nfc\",\n\t\t\t] as AuthenticatorTransport[],\n\t\t})),\n\t});\n\n\t// Store the challenge server-side for later verification\n\tawait accountDO.rpcSaveWebAuthnChallenge(options.challenge);\n\n\treturn options;\n}\n\n/**\n * Verify passkey authentication.\n * The challenge is validated against the server-stored value.\n */\nexport async function verifyPasskeyAuthentication(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tpdsHostname: string,\n\tresponse: AuthenticationResponseJSON,\n\tchallenge: string,\n): Promise<{ success: true } | { success: false; error: string }> {\n\ttry {\n\t\t// Verify the challenge was generated by the server and consume it (single-use)\n\t\tconst challengeValid =\n\t\t\tawait accountDO.rpcConsumeWebAuthnChallenge(challenge);\n\t\tif (!challengeValid) {\n\t\t\treturn { success: false, error: \"Invalid or expired challenge\" };\n\t\t}\n\n\t\t// Get the passkey from storage\n\t\tconst passkey = await accountDO.rpcGetPasskey(response.id);\n\t\tif (!passkey) {\n\t\t\treturn { success: false, error: \"Unknown credential\" };\n\t\t}\n\n\t\t// Verify the authentication response\n\t\tconst verification = await verifyAuthenticationResponse({\n\t\t\tresponse,\n\t\t\texpectedChallenge: challenge,\n\t\t\texpectedOrigin: `https://${pdsHostname}`,\n\t\t\texpectedRPID: pdsHostname,\n\t\t\tcredential: {\n\t\t\t\tid: passkey.credentialId,\n\t\t\t\tpublicKey: new Uint8Array(passkey.publicKey),\n\t\t\t\tcounter: passkey.counter,\n\t\t\t},\n\t\t});\n\n\t\tif (!verification.verified) {\n\t\t\treturn { success: false, error: \"Verification failed\" };\n\t\t}\n\n\t\t// Update the counter\n\t\tawait accountDO.rpcUpdatePasskeyCounter(\n\t\t\tresponse.id,\n\t\t\tverification.authenticationInfo.newCounter,\n\t\t);\n\n\t\treturn { success: true };\n\t} catch (err) {\n\t\tconsole.error(\"Passkey authentication error:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: err instanceof Error ? err.message : \"Authentication failed\",\n\t\t};\n\t}\n}\n\n/**\n * List all passkeys for the account\n */\nexport async function listPasskeys(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<PasskeyInfo[]> {\n\tconst passkeys = await accountDO.rpcListPasskeys();\n\treturn passkeys.map((pk) => ({\n\t\tid: pk.credentialId,\n\t\tname: pk.name,\n\t\tcreatedAt: pk.createdAt,\n\t\tlastUsedAt: pk.lastUsedAt,\n\t}));\n}\n\n/**\n * Delete a passkey\n */\nexport async function deletePasskey(\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n\tcredentialId: string,\n): Promise<boolean> {\n\treturn accountDO.rpcDeletePasskey(credentialId);\n}\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\";\nimport {\n\tgetAuthenticationOptions,\n\tverifyPasskeyAuthentication,\n\ttype AuthenticationResponseJSON,\n} from \"./passkey\";\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\t// Passkey authentication options\n\t\tgetPasskeyOptions: async (): Promise<Record<string, unknown> | null> => {\n\t\t\tconst options = await getAuthenticationOptions(\n\t\t\t\taccountDO,\n\t\t\t\tenv.PDS_HOSTNAME,\n\t\t\t);\n\t\t\treturn options as Record<string, unknown> | null;\n\t\t},\n\t\t// Passkey verification\n\t\tverifyPasskey: async (response, challenge: string) => {\n\t\t\tconst result = await verifyPasskeyAuthentication(\n\t\t\t\taccountDO,\n\t\t\t\tenv.PDS_HOSTNAME,\n\t\t\t\tresponse as AuthenticationResponseJSON,\n\t\t\t\tchallenge,\n\t\t\t);\n\t\t\tif (!result.success) 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\t// Messaging platform link preview bots pre-fetch URLs shared in DMs and\n\t\t// channels, which consumes the one-time PAR request URI before the user\n\t\t// can open it. Return a minimal HTML page for known preview bots instead\n\t\t// of processing the OAuth request. Only specific messaging platforms are\n\t\t// matched — generic crawlers and spiders should consume the token since\n\t\t// an unknown bot hitting an OAuth URL is legitimately suspicious.\n\t\tconst ua = c.req.header(\"User-Agent\") ?? \"\";\n\t\tif (\n\t\t\t/TelegramBot|Slackbot|Discordbot|Twitterbot|facebookexternalhit|WhatsApp/i.test(\n\t\t\t\tua,\n\t\t\t)\n\t\t) {\n\t\t\treturn c.html(`<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Cirrus Authorization</title>\n\t<meta name=\"description\" content=\"Cirrus PDS authorization page. Open this link in your browser to continue.\">\n\t<meta property=\"og:title\" content=\"Cirrus Authorization\">\n\t<meta property=\"og:description\" content=\"Open this link in your browser to continue.\">\n</head>\n<body>\n\t<p>Open this link in your browser to continue.</p>\n</body>\n</html>`);\n\t\t}\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// Passkey authentication endpoint\n\toauth.post(\"/oauth/passkey-auth\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\treturn provider.handlePasskeyAuth(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// UserInfo endpoint (OpenID Connect)\n\t// Returns user claims for the authenticated user\n\toauth.get(\"/oauth/userinfo\", async (c) => {\n\t\tconst provider = getProvider(c.env);\n\t\tconst tokenData = await provider.verifyAccessToken(c.req.raw);\n\n\t\tif (!tokenData) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"invalid_token\",\n\t\t\t\t\terror_description: \"Invalid or expired token\",\n\t\t\t\t},\n\t\t\t\t401,\n\t\t\t);\n\t\t}\n\n\t\t// Return OpenID Connect userinfo response\n\t\t// sub is required, we also include preferred_username (handle)\n\t\treturn c.json({\n\t\t\tsub: tokenData.sub,\n\t\t\tpreferred_username: c.env.HANDLE,\n\t\t});\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{\n\t\t\t\t\terror: \"invalid_request\",\n\t\t\t\t\terror_description: \"Failed to parse request body\",\n\t\t\t\t},\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, TokenExpiredError } 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: \"com.atproto.access\" });\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 (err) {\n\t\t// Match official PDS: expired tokens return 400 with 'ExpiredToken'\n\t\t// This is required for clients to trigger automatic token refresh\n\t\tif (err instanceof TokenExpiredError) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\t\t// Session JWT verification failed for other reasons, 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 * Uses @atcute/identity-resolver which is already Workers-compatible\n * (uses redirect: \"manual\" internally).\n */\n\nimport {\n\tCompositeDidDocumentResolver,\n\tPlcDidDocumentResolver,\n\tWebDidDocumentResolver,\n} from \"@atcute/identity-resolver\";\nimport type { DidDocument } from \"@atcute/identity\";\nimport type { Did } from \"@atcute/lexicons/syntax\";\nimport type { DidCache } from \"./did-cache\";\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\n// Re-export DidDocument for consumers\nexport type { DidDocument };\n\n/**\n * Wrapper that always uses globalThis.fetch so it can be mocked in tests.\n * @atcute resolvers capture the fetch reference at construction time,\n * so we need this indirection to allow test mocking.\n */\nconst stubbableFetch: typeof fetch = (input, init) =>\n\tglobalThis.fetch(input, init);\n\nexport class DidResolver {\n\tprivate resolver: CompositeDidDocumentResolver<\"plc\" | \"web\">;\n\tprivate timeout: number;\n\tprivate cache?: DidCache;\n\n\tconstructor(opts: DidResolverOpts = {}) {\n\t\tthis.timeout = opts.timeout ?? TIMEOUT_MS;\n\t\tthis.cache = opts.didCache;\n\n\t\tthis.resolver = new CompositeDidDocumentResolver({\n\t\t\tmethods: {\n\t\t\t\tplc: new PlcDidDocumentResolver({\n\t\t\t\t\tapiUrl: opts.plcUrl ?? PLC_DIRECTORY,\n\t\t\t\t\tfetch: stubbableFetch,\n\t\t\t\t}),\n\t\t\t\tweb: new WebDidDocumentResolver({\n\t\t\t\t\tfetch: stubbableFetch,\n\t\t\t\t}),\n\t\t\t},\n\t\t});\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\t// Create abort signal with timeout\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n\t\ttry {\n\t\t\t// @atcute resolver throws on errors, we return null\n\t\t\tconst doc = await this.resolver.resolve(did as Did<\"plc\" | \"web\">, {\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t\t// Validate that the returned document matches the requested DID\n\t\t\tif (doc.id !== did) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn doc;\n\t\t} catch {\n\t\t\treturn null;\n\t\t} finally {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t}\n}\n","/**\n * DID cache using Cloudflare Workers Cache API\n */\n\nimport { defs, type DidDocument } from \"@atcute/identity\";\nimport { waitUntil } from \"cloudflare:workers\";\n\n/**\n * Cache result from checking the DID cache.\n */\nexport interface CacheResult {\n\tdid: string;\n\tdoc: DidDocument;\n\tupdatedAt: number;\n\tstale: boolean;\n\texpired: boolean;\n}\n\n/**\n * Interface for DID document caching.\n */\nexport interface DidCache {\n\tcacheDid(\n\t\tdid: string,\n\t\tdoc: DidDocument,\n\t\tprevResult?: CacheResult,\n\t): Promise<void>;\n\tcheckCache(did: string): Promise<CacheResult | null>;\n\trefreshCache(\n\t\tdid: string,\n\t\tgetDoc: () => Promise<DidDocument | null>,\n\t\tprevResult?: CacheResult,\n\t): Promise<void>;\n\tclearEntry(did: string): Promise<void>;\n\tclear(): Promise<void>;\n}\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\t// Validate cached document schema\n\t\tconst parsed = defs.didDocument.try(await response.json());\n\t\tif (!parsed.ok || parsed.value.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: parsed.value,\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 { getAtprotoServiceEndpoint } from \"@atcute/identity\";\nimport { createServiceJwt } from \"./service-auth\";\nimport { verifyAccessToken, TokenExpiredError } 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 = getAtprotoServiceEndpoint(didDoc, {\n\t\t\t\tid: serviceId as `#${string}`,\n\t\t\t});\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 (err) {\n\t\t\t// Match official PDS: expired tokens return 400 with 'ExpiredToken'\n\t\t\t// This is required for clients to trigger automatic token refresh\n\t\t\tif (err instanceof TokenExpiredError) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"ExpiredToken\",\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\t// Other token verification errors - 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","/**\n * Detect content type from file magic bytes.\n * Returns the detected MIME type or null if unknown.\n */\nexport function detectContentType(bytes: Uint8Array): string | null {\n\t// MP4/M4V/MOV - check for ftyp box\n\tif (bytes.length >= 12) {\n\t\tconst ftyp = String.fromCharCode(\n\t\t\tbytes[4]!,\n\t\t\tbytes[5]!,\n\t\t\tbytes[6]!,\n\t\t\tbytes[7]!,\n\t\t);\n\t\tif (ftyp === \"ftyp\") {\n\t\t\t// Check brand for more specific type\n\t\t\tconst brand = String.fromCharCode(\n\t\t\t\tbytes[8]!,\n\t\t\t\tbytes[9]!,\n\t\t\t\tbytes[10]!,\n\t\t\t\tbytes[11]!,\n\t\t\t);\n\t\t\tif (\n\t\t\t\tbrand === \"isom\" ||\n\t\t\t\tbrand === \"iso2\" ||\n\t\t\t\tbrand === \"mp41\" ||\n\t\t\t\tbrand === \"mp42\" ||\n\t\t\t\tbrand === \"avc1\"\n\t\t\t) {\n\t\t\t\treturn \"video/mp4\";\n\t\t\t}\n\t\t\tif (brand === \"M4V \" || brand === \"M4VH\" || brand === \"M4VP\") {\n\t\t\t\treturn \"video/x-m4v\";\n\t\t\t}\n\t\t\tif (brand === \"qt \") {\n\t\t\t\treturn \"video/quicktime\";\n\t\t\t}\n\t\t\t// Default to mp4 for any ftyp\n\t\t\treturn \"video/mp4\";\n\t\t}\n\t}\n\n\t// JPEG\n\tif (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {\n\t\treturn \"image/jpeg\";\n\t}\n\n\t// PNG\n\tif (\n\t\tbytes[0] === 0x89 &&\n\t\tbytes[1] === 0x50 &&\n\t\tbytes[2] === 0x4e &&\n\t\tbytes[3] === 0x47\n\t) {\n\t\treturn \"image/png\";\n\t}\n\n\t// GIF\n\tif (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {\n\t\treturn \"image/gif\";\n\t}\n\n\t// WebP\n\tif (\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\treturn \"image/webp\";\n\t}\n\n\t// WebM\n\tif (\n\t\tbytes[0] === 0x1a &&\n\t\tbytes[1] === 0x45 &&\n\t\tbytes[2] === 0xdf &&\n\t\tbytes[3] === 0xa3\n\t) {\n\t\treturn \"video/webm\";\n\t}\n\n\treturn null;\n}\n","import type { Context } from \"hono\";\nimport { isDid, isNsid, isRecordKey } from \"@atcute/lexicons/syntax\";\nimport type { AccountDurableObject } from \"../account-do.js\";\nimport type { AppEnv } from \"../types.js\";\nimport { detectContentType } from \"../format.js\";\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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// Stream through the DO's fetch handler to avoid buffering the entire CAR\n\treturn accountDO.fetch(\n\t\tnew Request(\"https://do/xrpc/com.atproto.sync.getRepo\"),\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\t// Determine content type, with fallback for missing or invalid values\n\tlet contentType = blob.httpMetadata?.contentType;\n\n\t// If no content type or invalid wildcard, try to detect from content\n\tif (!contentType || contentType === \"*/*\") {\n\t\t// Read first few bytes to detect content type\n\t\tconst [headerStream, bodyStream] = blob.body.tee();\n\t\tconst reader = headerStream.getReader();\n\t\tconst { value: headerBytes } = await reader.read();\n\t\treader.releaseLock();\n\n\t\tif (headerBytes && headerBytes.length >= 12) {\n\t\t\tcontentType =\n\t\t\t\tdetectContentType(headerBytes) || \"application/octet-stream\";\n\t\t} else {\n\t\t\tcontentType = \"application/octet-stream\";\n\t\t}\n\n\t\treturn new Response(bodyStream, {\n\t\t\tstatus: 200,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": contentType,\n\t\t\t\t\"Content-Length\": blob.size.toString(),\n\t\t\t},\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\": contentType,\n\t\t\t\"Content-Length\": blob.size.toString(),\n\t\t},\n\t});\n}\n\nexport async function getRecord(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst did = c.req.query(\"did\");\n\tconst collection = c.req.query(\"collection\");\n\tconst rkey = c.req.query(\"rkey\");\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 (!collection) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: collection\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tif (!rkey) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: rkey\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate DID format\n\tif (!isDid(did)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate collection is an NSID\n\tif (!isNsid(collection)) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Invalid collection format (must be NSID)\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate rkey format\n\tif (!isRecordKey(rkey)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid rkey format\" },\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Check if this is our DID\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\ttry {\n\t\tconst carBytes = await accountDO.rpcGetRecordProof(collection, rkey);\n\n\t\treturn new Response(carBytes, {\n\t\t\tstatus: 200,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/vnd.ipld.car\",\n\t\t\t\t\"Content-Length\": carBytes.length.toString(),\n\t\t\t},\n\t\t});\n\t} catch (err) {\n\t\t// The proof CAR will still be returned even if the record doesn't exist\n\t\t// (to prove non-existence), so errors here indicate storage issues\n\t\tconsole.error(\"Error getting record proof:\", err);\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: \"Failed to get record proof\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n}\n","import {\n\tparse,\n\tValidationError,\n\ttype BaseSchema,\n} from \"@atcute/lexicons/validations\";\n\n// Import record schemas from @atcute/bluesky\nimport {\n\tAppBskyActorProfile,\n\tAppBskyFeedGenerator,\n\tAppBskyFeedLike,\n\tAppBskyFeedPost,\n\tAppBskyFeedPostgate,\n\tAppBskyFeedRepost,\n\tAppBskyFeedThreadgate,\n\tAppBskyGraphBlock,\n\tAppBskyGraphFollow,\n\tAppBskyGraphList,\n\tAppBskyGraphListblock,\n\tAppBskyGraphListitem,\n\tAppBskyGraphStarterpack,\n\tAppBskyGraphVerification,\n\tAppBskyLabelerService,\n} from \"@atcute/bluesky\";\n\n/**\n * Map of collection NSID to validation schema.\n * Only includes record types that can be created in repositories.\n */\nconst recordSchemas: Record<string, BaseSchema> = {\n\t\"app.bsky.actor.profile\": AppBskyActorProfile.mainSchema,\n\t\"app.bsky.feed.generator\": AppBskyFeedGenerator.mainSchema,\n\t\"app.bsky.feed.like\": AppBskyFeedLike.mainSchema,\n\t\"app.bsky.feed.post\": AppBskyFeedPost.mainSchema,\n\t\"app.bsky.feed.postgate\": AppBskyFeedPostgate.mainSchema,\n\t\"app.bsky.feed.repost\": AppBskyFeedRepost.mainSchema,\n\t\"app.bsky.feed.threadgate\": AppBskyFeedThreadgate.mainSchema,\n\t\"app.bsky.graph.block\": AppBskyGraphBlock.mainSchema,\n\t\"app.bsky.graph.follow\": AppBskyGraphFollow.mainSchema,\n\t\"app.bsky.graph.list\": AppBskyGraphList.mainSchema,\n\t\"app.bsky.graph.listblock\": AppBskyGraphListblock.mainSchema,\n\t\"app.bsky.graph.listitem\": AppBskyGraphListitem.mainSchema,\n\t\"app.bsky.graph.starterpack\": AppBskyGraphStarterpack.mainSchema,\n\t\"app.bsky.graph.verification\": AppBskyGraphVerification.mainSchema,\n\t\"app.bsky.labeler.service\": AppBskyLabelerService.mainSchema,\n};\n\n/**\n * Record validator for AT Protocol records.\n *\n * Validates records against official Bluesky lexicon schemas from @atcute/bluesky.\n * Uses optimistic validation strategy:\n * - If a 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.\n */\nexport class RecordValidator {\n\tprivate strictMode: boolean;\n\n\tconstructor(options: { strict?: boolean } = {}) {\n\t\tthis.strictMode = options.strict ?? false;\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\tconst schema = recordSchemas[collection];\n\n\t\tif (!schema) {\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\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tparse(schema, record);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ValidationError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Lexicon validation failed for ${collection}: ${error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\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\treturn collection in recordSchemas;\n\t}\n\n\t/**\n\t * Get list of all loaded schema NSIDs.\n\t */\n\tgetLoadedSchemas(): string[] {\n\t\treturn Object.keys(recordSchemas);\n\t}\n}\n\n/**\n * Shared validator instance (singleton pattern).\n * Uses optimistic validation by default (strict: false).\n */\nexport const validator = new RecordValidator({ strict: false });\n","import type { Context } from \"hono\";\nimport { isDid } from \"@atcute/lexicons/syntax\";\nimport { AccountDurableObject } from \"../account-do.js\";\nimport type { AppEnv, AuthedAppEnv } from \"../types.js\";\nimport { validator } from \"../validation.js\";\nimport { detectContentType } from \"../format.js\";\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\tif (!isDid(repo)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tif (!isDid(repo)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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: `at://${repo}/${collection}/${rkey}`,\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\tif (!isDid(repo)) {\n\t\treturn c.json(\n\t\t\t{ error: \"InvalidRequest\", message: \"Invalid DID format\" },\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\tlet contentType = c.req.header(\"Content-Type\");\n\n\tconst bytes = new Uint8Array(await c.req.arrayBuffer());\n\tif (!contentType || contentType === \"*/*\") {\n\t\tcontentType = detectContentType(bytes) || \"application/octet-stream\";\n\t}\n\n\t// Size limit check (60MB)\n\tconst MAX_BLOB_SIZE = 60 * 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\tTokenExpiredError,\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(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): 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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\tconst email = storedEmail || c.env.EMAIL;\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\t...(email ? { email } : {}),\n\t\temailConfirmed: true,\n\t\tactive: true,\n\t});\n}\n\n/**\n * Refresh a session\n */\nexport async function refreshSession(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): 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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\t\tconst email = storedEmail || c.env.EMAIL;\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\t...(email ? { email } : {}),\n\t\t\temailConfirmed: true,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\t// Match official PDS: expired tokens return 'ExpiredToken', other errors return 'InvalidToken'\n\t\tif (err instanceof TokenExpiredError) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\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 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(\n\tc: Context<AppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): 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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\t\tconst email = storedEmail || c.env.EMAIL;\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\t...(email ? { email } : {}),\n\t\t\temailConfirmed: true,\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\tconst { email: storedEmail } = await accountDO.rpcGetEmail();\n\t\tconst email = storedEmail || c.env.EMAIL;\n\t\treturn c.json({\n\t\t\thandle: c.env.HANDLE,\n\t\t\tdid: c.env.DID,\n\t\t\t...(email ? { email } : {}),\n\t\t\temailConfirmed: true,\n\t\t\tactive: true,\n\t\t});\n\t} catch (err) {\n\t\t// Match official PDS: expired tokens return 400 with 'ExpiredToken'\n\t\t// This is required for clients to trigger automatic token refresh\n\t\tif (err instanceof TokenExpiredError) {\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"ExpiredToken\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t\t400,\n\t\t\t);\n\t\t}\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 checkAccountStatus(\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\t// Account is considered \"activated\" if it's currently active OR has content\n\t\tconst activated = active || indexedRecords > 0;\n\n\t\treturn c.json({\n\t\t\tactivated,\n\t\t\tactive,\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\tactivated: false,\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 * Request a token to update the account email.\n * Single-user PDS: no token needed, always returns tokenRequired: false.\n */\nexport async function requestEmailUpdate(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\treturn c.json({ tokenRequired: false });\n}\n\n/**\n * Request email confirmation.\n * Single-user PDS: email is always confirmed, nothing to do.\n */\nexport async function requestEmailConfirmation(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\treturn c.json({});\n}\n\n/**\n * Update the account email address\n */\nexport async function updateEmail(\n\tc: Context<AuthedAppEnv>,\n\taccountDO: DurableObjectStub<AccountDurableObject>,\n): Promise<Response> {\n\tconst body = await c.req.json<{ email: string }>();\n\n\tif (!body.email) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required field: email\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\tawait accountDO.rpcUpdateEmail(body.email);\n\treturn c.json({});\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","/**\n * Stateless migration tokens for outbound account migration\n *\n * Uses HMAC-SHA256 to create tokens that encode the DID and expiry time.\n * No database storage required - validity is verified by the signature.\n *\n * Token format: base64url(payload).base64url(signature)\n * Payload: {\"did\":\"did:plc:xxx\",\"exp\":1736600000}\n *\n * Tokens expire after 15 minutes - enough time to complete the migration\n * process but short enough to limit exposure if the token is leaked.\n */\n\nimport { base64url } from \"jose\";\n\nconst MINUTE = 60 * 1000;\nconst TOKEN_EXPIRY = 15 * MINUTE; // 15 minutes\n\ninterface TokenPayload {\n\tdid: string;\n\texp: number;\n}\n\n/**\n * Create an HMAC-SHA256 signature\n */\nasync function hmacSign(data: string, secret: string): Promise<ArrayBuffer> {\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"sign\"],\n\t);\n\treturn crypto.subtle.sign(\"HMAC\", key, encoder.encode(data));\n}\n\n/**\n * Verify an HMAC-SHA256 signature\n */\nasync function hmacVerify(\n\tdata: string,\n\tsignature: ArrayBuffer,\n\tsecret: string,\n): Promise<boolean> {\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\treturn crypto.subtle.verify(\"HMAC\", key, signature, encoder.encode(data));\n}\n\n/**\n * Create a migration token for outbound migration\n *\n * @param did - The user's DID\n * @param jwtSecret - The JWT_SECRET used for signing\n * @returns A stateless, signed token\n */\nexport async function createMigrationToken(\n\tdid: string,\n\tjwtSecret: string,\n): Promise<string> {\n\tconst exp = Math.floor((Date.now() + TOKEN_EXPIRY) / 1000);\n\tconst payload: TokenPayload = { did, exp };\n\n\tconst payloadStr = JSON.stringify(payload);\n\tconst payloadB64 = base64url.encode(new TextEncoder().encode(payloadStr));\n\n\tconst signature = await hmacSign(payloadB64, jwtSecret);\n\tconst signatureB64 = base64url.encode(new Uint8Array(signature));\n\n\treturn `${payloadB64}.${signatureB64}`;\n}\n\n/**\n * Validate a migration token\n *\n * @param token - The token to validate\n * @param expectedDid - The expected DID (must match token payload)\n * @param jwtSecret - The JWT_SECRET used for verification\n * @returns The payload if valid, null if invalid/expired\n */\nexport async function validateMigrationToken(\n\ttoken: string,\n\texpectedDid: string,\n\tjwtSecret: string,\n): Promise<TokenPayload | null> {\n\tconst parts = token.split(\".\");\n\tif (parts.length !== 2) {\n\t\treturn null;\n\t}\n\n\tconst [payloadB64, signatureB64] = parts as [string, string];\n\n\ttry {\n\t\t// Verify signature\n\t\tconst signatureBytes = base64url.decode(signatureB64);\n\t\tconst isValid = await hmacVerify(\n\t\t\tpayloadB64,\n\t\t\tsignatureBytes.buffer,\n\t\t\tjwtSecret,\n\t\t);\n\t\tif (!isValid) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Decode and validate payload\n\t\tconst payloadStr = new TextDecoder().decode(base64url.decode(payloadB64));\n\t\tconst payload: TokenPayload = JSON.parse(payloadStr);\n\n\t\t// Check DID matches\n\t\tif (payload.did !== expectedDid) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check expiry\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tif (payload.exp < now) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn payload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n","/**\n * Identity XRPC endpoints for outbound migration\n *\n * These endpoints allow migrating FROM Cirrus to another PDS.\n *\n * Flow:\n * 1. New PDS calls requestPlcOperationSignature (after user authenticates)\n * 2. We generate a migration token (stateless HMAC)\n * 3. User runs `pds migrate-token` CLI to get the token\n * 4. User enters token into new PDS\n * 5. New PDS calls signPlcOperation with token + new endpoint/key\n * 6. We validate token and return signed PLC operation\n * 7. New PDS submits operation to PLC directory\n */\nimport type { Context } from \"hono\";\nimport { Secp256k1Keypair } from \"@atproto/crypto\";\nimport { encode } from \"@atcute/cbor\";\nimport { base64url } from \"jose\";\nimport type { AuthedAppEnv } from \"../types\";\nimport {\n\tcreateMigrationToken,\n\tvalidateMigrationToken,\n} from \"../migration-token\";\n\nconst PLC_DIRECTORY = \"https://plc.directory\";\n\n/**\n * PLC operation structure\n */\ninterface UnsignedPlcOperation {\n\ttype: \"plc_operation\";\n\tprev: string | null;\n\trotationKeys: string[];\n\tverificationMethods: Record<string, string>;\n\talsoKnownAs: string[];\n\tservices: Record<string, { type: string; endpoint: string }>;\n}\n\ninterface SignedPlcOperation extends UnsignedPlcOperation {\n\tsig: string;\n}\n\n/**\n * Audit log entry from plc.directory\n */\ninterface PlcAuditLog {\n\tdid: string;\n\toperation: SignedPlcOperation;\n\tcid: string;\n\tnullified: boolean;\n\tcreatedAt: string;\n}\n\n/**\n * Request a PLC operation signature for outbound migration.\n *\n * In Bluesky's implementation, this sends an email with a token.\n * In Cirrus, we're single-user with no email, so we just return success.\n * The user gets the token via `pds migrate-token` CLI.\n *\n * Endpoint: POST com.atproto.identity.requestPlcOperationSignature\n */\nexport async function requestPlcOperationSignature(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\t// For Cirrus, we don't send emails - the user gets the token via CLI.\n\t// Just return success to indicate the request was accepted.\n\t// The token is generated on-demand when the user runs `pds migrate-token`.\n\treturn new Response(null, { status: 200 });\n}\n\n/**\n * Sign a PLC operation for migrating to a new PDS.\n *\n * Validates the migration token and returns a signed PLC operation\n * that updates the DID document to point to the new PDS.\n *\n * Endpoint: POST com.atproto.identity.signPlcOperation\n */\nexport async function signPlcOperation(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\tconst body = await c.req.json<{\n\t\ttoken?: string;\n\t\trotationKeys?: string[];\n\t\talsoKnownAs?: string[];\n\t\tverificationMethods?: Record<string, string>;\n\t\tservices?: Record<string, { type: string; endpoint: string }>;\n\t}>();\n\n\tconst { token } = body;\n\n\tif (!token) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidRequest\",\n\t\t\t\tmessage: \"Missing required parameter: token\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Validate the migration token\n\tconst payload = await validateMigrationToken(\n\t\ttoken,\n\t\tc.env.DID,\n\t\tc.env.JWT_SECRET,\n\t);\n\n\tif (!payload) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InvalidToken\",\n\t\t\t\tmessage: \"Invalid or expired migration token\",\n\t\t\t},\n\t\t\t400,\n\t\t);\n\t}\n\n\t// Get current PLC state to build the update\n\tconst currentOp = await getLatestPlcOperation(c.env.DID);\n\tif (!currentOp) {\n\t\treturn c.json(\n\t\t\t{\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: \"Could not fetch current PLC state\",\n\t\t\t},\n\t\t\t500,\n\t\t);\n\t}\n\n\t// Build the new operation, merging current state with requested changes\n\tconst newOp: UnsignedPlcOperation = {\n\t\ttype: \"plc_operation\",\n\t\tprev: currentOp.cid,\n\t\trotationKeys: body.rotationKeys ?? currentOp.operation.rotationKeys,\n\t\talsoKnownAs: body.alsoKnownAs ?? currentOp.operation.alsoKnownAs,\n\t\tverificationMethods:\n\t\t\tbody.verificationMethods ?? currentOp.operation.verificationMethods,\n\t\tservices: body.services ?? currentOp.operation.services,\n\t};\n\n\t// Sign the operation with our signing key\n\tconst keypair = await Secp256k1Keypair.import(c.env.SIGNING_KEY);\n\tconst signedOp = await signOperation(newOp, keypair);\n\n\treturn c.json({ operation: signedOp });\n}\n\n/**\n * Get the latest PLC operation for a DID\n */\nasync function getLatestPlcOperation(did: string): Promise<PlcAuditLog | null> {\n\ttry {\n\t\tconst res = await fetch(`${PLC_DIRECTORY}/${did}/log/audit`);\n\t\tif (!res.ok) {\n\t\t\treturn null;\n\t\t}\n\t\tconst log = (await res.json()) as PlcAuditLog[];\n\t\t// Return the most recent non-nullified operation\n\t\treturn log.filter((op) => !op.nullified).pop() ?? null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Sign a PLC operation with the given keypair\n *\n * PLC operations are signed by:\n * 1. CBOR-encoding the unsigned operation\n * 2. Signing the bytes with secp256k1\n * 3. Adding the signature as base64url\n */\nasync function signOperation(\n\top: UnsignedPlcOperation,\n\tkeypair: Secp256k1Keypair,\n): Promise<SignedPlcOperation> {\n\t// CBOR-encode the operation (without sig field)\n\tconst bytes = encode(op);\n\n\t// Sign the bytes\n\tconst sig = await keypair.sign(bytes);\n\n\t// Convert signature to base64url\n\treturn {\n\t\t...op,\n\t\tsig: base64url.encode(sig),\n\t};\n}\n\n/**\n * Generate a migration token for the CLI.\n *\n * This endpoint allows the CLI to generate a token that can be used\n * to complete an outbound migration without requiring the secret\n * to be available client-side.\n *\n * Endpoint: GET gg.mk.experimental.getMigrationToken\n */\nexport async function getMigrationToken(\n\tc: Context<AuthedAppEnv>,\n): Promise<Response> {\n\tconst token = await createMigrationToken(c.env.DID, c.env.JWT_SECRET);\n\treturn c.json({ token });\n}\n","/**\n * Passkey Registration UI\n *\n * Renders the HTML page for passkey registration.\n * Matches the styling of the OAuth consent UI.\n */\n\nimport type { PublicKeyCredentialCreationOptionsJSON } from \"./passkey\";\n\n/**\n * The main registration script (static, can be hashed).\n * Dynamic data is passed via data attributes on the script element.\n */\nconst PASSKEY_REGISTRATION_SCRIPT = `\n// Get dynamic data from script element\nconst scriptEl = document.currentScript;\nconst options = JSON.parse(scriptEl.dataset.options);\nconst token = scriptEl.dataset.token;\n\n// Convert base64url challenge to ArrayBuffer\nfunction base64urlToBuffer(base64url) {\n\tconst base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n\tconst padding = '='.repeat((4 - base64.length % 4) % 4);\n\tconst binary = atob(base64 + padding);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes.buffer;\n}\n\n// Convert ArrayBuffer to base64url\nfunction bufferToBase64url(buffer) {\n\tconst bytes = new Uint8Array(buffer);\n\tlet binary = '';\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i]);\n\t}\n\treturn btoa(binary)\n\t\t.replace(/\\\\+/g, '-')\n\t\t.replace(/\\\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nasync function registerPasskey() {\n\tconst btn = document.getElementById('register-btn');\n\tconst status = document.getElementById('status');\n\n\tbtn.disabled = true;\n\tbtn.textContent = 'Registering...';\n\tstatus.textContent = '';\n\tstatus.className = 'status';\n\n\ttry {\n\t\t// Convert options for WebAuthn API\n\t\tconst publicKeyOptions = {\n\t\t\tchallenge: base64urlToBuffer(options.challenge),\n\t\t\trp: options.rp,\n\t\t\tuser: {\n\t\t\t\tid: base64urlToBuffer(options.user.id),\n\t\t\t\tname: options.user.name,\n\t\t\t\tdisplayName: options.user.displayName,\n\t\t\t},\n\t\t\tpubKeyCredParams: options.pubKeyCredParams,\n\t\t\ttimeout: options.timeout,\n\t\t\tattestation: options.attestation,\n\t\t\tauthenticatorSelection: options.authenticatorSelection,\n\t\t\texcludeCredentials: (options.excludeCredentials || []).map(cred => ({\n\t\t\t\tid: base64urlToBuffer(cred.id),\n\t\t\t\ttype: cred.type,\n\t\t\t\ttransports: cred.transports,\n\t\t\t})),\n\t\t};\n\n\t\t// Perform WebAuthn ceremony\n\t\tconst credential = await navigator.credentials.create({\n\t\t\tpublicKey: publicKeyOptions\n\t\t});\n\n\t\tif (!credential) {\n\t\t\tthrow new Error('No credential returned');\n\t\t}\n\n\t\t// Prepare response for server\n\t\tconst response = {\n\t\t\tid: credential.id,\n\t\t\trawId: bufferToBase64url(credential.rawId),\n\t\t\tresponse: {\n\t\t\t\tclientDataJSON: bufferToBase64url(credential.response.clientDataJSON),\n\t\t\t\tattestationObject: bufferToBase64url(credential.response.attestationObject),\n\t\t\t\ttransports: credential.response.getTransports ? credential.response.getTransports() : [],\n\t\t\t},\n\t\t\ttype: credential.type,\n\t\t\tclientExtensionResults: credential.getClientExtensionResults(),\n\t\t\tauthenticatorAttachment: credential.authenticatorAttachment,\n\t\t};\n\n\t\t// Submit to server\n\t\tconst result = await fetch('/passkey/register', {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t\tbody: JSON.stringify({ token, response }),\n\t\t});\n\n\t\tconst data = await result.json();\n\n\t\tif (data.success) {\n\t\t\t// Show success UI\n\t\t\tdocument.getElementById('register-container').style.display = 'none';\n\t\t\tdocument.getElementById('success-container').style.display = 'block';\n\t\t} else {\n\t\t\tthrow new Error(data.error || 'Registration failed');\n\t\t}\n\t} catch (err) {\n\t\tconsole.error('Registration error:', err);\n\t\tstatus.textContent = err.message || 'Registration failed. Please try again.';\n\t\tstatus.className = 'status error';\n\t\tbtn.disabled = false;\n\t\tbtn.textContent = 'Register Passkey';\n\t}\n}\n\n// Check if WebAuthn is supported\nif (!window.PublicKeyCredential) {\n\tdocument.getElementById('status').textContent = 'WebAuthn is not supported in this browser.';\n\tdocument.getElementById('status').className = 'status error';\n\tdocument.getElementById('register-btn').disabled = true;\n} else {\n\tdocument.getElementById('register-btn').addEventListener('click', registerPasskey);\n}\n`;\n\n/**\n * Compute SHA-256 hash for CSP script-src\n */\nasync function computeScriptHash(script: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(script);\n\tconst hashBuffer = await crypto.subtle.digest(\"SHA-256\", data);\n\tconst hashArray = Array.from(new Uint8Array(hashBuffer));\n\tconst base64Hash = btoa(String.fromCharCode(...hashArray));\n\treturn `'sha256-${base64Hash}'`;\n}\n\n// Pre-computed hash (computed at module load, will be a Promise)\nlet registrationScriptHashPromise: Promise<string> | null = null;\n\n/**\n * Get the script hash for the passkey registration script\n */\nasync function getPasskeyScriptHash(): Promise<string> {\n\tif (!registrationScriptHashPromise) {\n\t\tregistrationScriptHashPromise = computeScriptHash(\n\t\t\tPASSKEY_REGISTRATION_SCRIPT,\n\t\t);\n\t}\n\treturn registrationScriptHashPromise;\n}\n\n/**\n * Content Security Policy for the passkey UI (computed dynamically with script hash)\n */\nexport async function getPasskeyUiCsp(): Promise<string> {\n\tconst scriptHash = await getPasskeyScriptHash();\n\treturn `default-src 'none'; script-src ${scriptHash}; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'`;\n}\n\n/**\n * Content Security Policy for error pages (no scripts)\n */\nexport const PASSKEY_ERROR_CSP =\n\t\"default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'\";\n\n/**\n * Escape HTML to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\");\n}\n\nexport interface PasskeyUIOptions {\n\t/** WebAuthn registration options */\n\toptions: PublicKeyCredentialCreationOptionsJSON;\n\t/** Token for the registration */\n\ttoken: string;\n\t/** User's handle */\n\thandle: string;\n}\n\n/**\n * Render the passkey registration page\n */\nexport function renderPasskeyRegistrationPage(opts: PasskeyUIOptions): string {\n\tconst { options, token, handle } = opts;\n\n\t// Serialize options for data attribute (HTML-escaped JSON)\n\tconst optionsAttr = escapeHtml(JSON.stringify(options));\n\tconst tokenAttr = escapeHtml(token);\n\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Register Passkey</title>\n\t<style>\n\t\t* {\n\t\t\tbox-sizing: border-box;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n\n\t\tbody {\n\t\t\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n\t\t\tbackground: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n\t\t\tmin-height: 100vh;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tpadding: 20px;\n\t\t\tcolor: #e0e0e0;\n\t\t}\n\n\t\t.container {\n\t\t\tbackground: #1e1e30;\n\t\t\tborder-radius: 16px;\n\t\t\tpadding: 32px;\n\t\t\tmax-width: 400px;\n\t\t\twidth: 100%;\n\t\t\tbox-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n\t\t\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t.icon {\n\t\t\twidth: 64px;\n\t\t\theight: 64px;\n\t\t\tborder-radius: 12px;\n\t\t\tmargin: 0 auto 16px;\n\t\t\tbackground: linear-gradient(135deg, #3b82f6, #8b5cf6);\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tfont-size: 28px;\n\t\t}\n\n\t\th1 {\n\t\t\tfont-size: 20px;\n\t\t\tfont-weight: 600;\n\t\t\tmargin-bottom: 8px;\n\t\t}\n\n\t\t.handle {\n\t\t\tfont-size: 14px;\n\t\t\tcolor: #60a5fa;\n\t\t\tmargin-bottom: 24px;\n\t\t}\n\n\t\t.info {\n\t\t\tbackground: rgba(255, 255, 255, 0.05);\n\t\t\tborder-radius: 12px;\n\t\t\tpadding: 16px;\n\t\t\tmargin-bottom: 24px;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: #9ca3af;\n\t\t\ttext-align: left;\n\t\t}\n\n\t\t.info p {\n\t\t\tmargin-bottom: 8px;\n\t\t}\n\n\t\t.info p:last-child {\n\t\t\tmargin-bottom: 0;\n\t\t}\n\n\t\t.btn {\n\t\t\twidth: 100%;\n\t\t\tpadding: 14px 24px;\n\t\t\tborder-radius: 8px;\n\t\t\tfont-size: 16px;\n\t\t\tfont-weight: 500;\n\t\t\tcursor: pointer;\n\t\t\ttransition: all 0.2s;\n\t\t\tborder: none;\n\t\t\tbackground: linear-gradient(135deg, #3b82f6, #2563eb);\n\t\t\tcolor: white;\n\t\t}\n\n\t\t.btn:hover:not(:disabled) {\n\t\t\tbackground: linear-gradient(135deg, #2563eb, #1d4ed8);\n\t\t}\n\n\t\t.btn:disabled {\n\t\t\topacity: 0.5;\n\t\t\tcursor: not-allowed;\n\t\t}\n\n\t\t.status {\n\t\t\tmargin-top: 16px;\n\t\t\tfont-size: 14px;\n\t\t\tmin-height: 20px;\n\t\t}\n\n\t\t.status.error {\n\t\t\tcolor: #f87171;\n\t\t}\n\n\t\t.status.success {\n\t\t\tcolor: #22c55e;\n\t\t}\n\n\t\t.success-container {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\t.success-icon {\n\t\t\twidth: 64px;\n\t\t\theight: 64px;\n\t\t\tborder-radius: 50%;\n\t\t\tmargin: 0 auto 16px;\n\t\t\tbackground: rgba(34, 197, 94, 0.1);\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tfont-size: 32px;\n\t\t\tcolor: #22c55e;\n\t\t}\n\n\t\t.close-info {\n\t\t\tmargin-top: 24px;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: #6b7280;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"container\" id=\"register-container\">\n\t\t<div class=\"icon\">🔐</div>\n\t\t<h1>Register Passkey</h1>\n\t\t<p class=\"handle\">@${escapeHtml(handle)}</p>\n\n\t\t<div class=\"info\">\n\t\t\t<p>A passkey lets you sign in securely using your device's biometrics (Face ID, fingerprint) or PIN.</p>\n\t\t\t<p>Click the button below to create a passkey for this device.</p>\n\t\t</div>\n\n\t\t<button class=\"btn\" id=\"register-btn\">\n\t\t\tRegister Passkey\n\t\t</button>\n\n\t\t<div class=\"status\" id=\"status\"></div>\n\t</div>\n\n\t<div class=\"container success-container\" id=\"success-container\">\n\t\t<div class=\"success-icon\">✓</div>\n\t\t<h1>Passkey Registered!</h1>\n\t\t<p class=\"handle\">@${escapeHtml(handle)}</p>\n\n\t\t<div class=\"info\">\n\t\t\t<p>Your passkey has been registered successfully.</p>\n\t\t\t<p>You can now use it to sign in to your account.</p>\n\t\t</div>\n\n\t\t<p class=\"close-info\">You can close this window.</p>\n\t</div>\n\n\t<script data-options=\"${optionsAttr}\" data-token=\"${tokenAttr}\">${PASSKEY_REGISTRATION_SCRIPT}</script>\n</body>\n</html>`;\n}\n\n/**\n * Render an error page\n */\nexport function renderPasskeyErrorPage(\n\terror: string,\n\tdescription: string,\n): string {\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Passkey Error</title>\n\t<style>\n\t\t* {\n\t\t\tbox-sizing: border-box;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n\n\t\tbody {\n\t\t\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n\t\t\tbackground: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n\t\t\tmin-height: 100vh;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tpadding: 20px;\n\t\t\tcolor: #e0e0e0;\n\t\t}\n\n\t\t.container {\n\t\t\tbackground: #1e1e30;\n\t\t\tborder-radius: 16px;\n\t\t\tpadding: 32px;\n\t\t\tmax-width: 400px;\n\t\t\twidth: 100%;\n\t\t\tbox-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n\t\t\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t.error-icon {\n\t\t\twidth: 64px;\n\t\t\theight: 64px;\n\t\t\tbackground: rgba(239, 68, 68, 0.1);\n\t\t\tborder-radius: 50%;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tmargin: 0 auto 16px;\n\t\t\tfont-size: 32px;\n\t\t}\n\n\t\th1 {\n\t\t\tfont-size: 20px;\n\t\t\tmargin-bottom: 8px;\n\t\t\tcolor: #f87171;\n\t\t}\n\n\t\tp {\n\t\t\tcolor: #9ca3af;\n\t\t\tfont-size: 14px;\n\t\t}\n\n\t\tcode {\n\t\t\tbackground: rgba(255, 255, 255, 0.1);\n\t\t\tpadding: 2px 6px;\n\t\t\tborder-radius: 4px;\n\t\t\tfont-size: 12px;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"container\">\n\t\t<div class=\"error-icon\">!</div>\n\t\t<h1>Passkey Error</h1>\n\t\t<p>${escapeHtml(description)}</p>\n\t\t<p style=\"margin-top: 8px;\"><code>${escapeHtml(error)}</code></p>\n\t</div>\n</body>\n</html>`;\n}\n","","// Public API\nexport { AccountDurableObject } from \"./account-do\";\nexport type { PDSEnv, DataLocation } 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 { isDid, isHandle } from \"@atcute/lexicons/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 * as identity from \"./xrpc/identity\";\nimport * as passkey from \"./passkey\";\nimport {\n\trenderPasskeyRegistrationPage,\n\trenderPasskeyErrorPage,\n\tgetPasskeyUiCsp,\n\tPASSKEY_ERROR_CSP,\n} from \"./passkey-ui\";\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\nif (!isDid(env.DID)) {\n\tthrow new Error(`Invalid DID format: ${env.DID}`);\n}\nif (!isHandle(env.HANDLE)) {\n\tthrow new Error(`Invalid handle format: ${env.HANDLE}`);\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 with optional data location\nfunction getAccountDO(env: PDSEnv) {\n\tconst location = env.DATA_LOCATION;\n\n\t// \"eu\" is a jurisdiction (hard guarantee), everything else is a hint (best-effort)\n\tif (location === \"eu\") {\n\t\tconst namespace = env.ACCOUNT.jurisdiction(\"eu\");\n\t\treturn namespace.get(namespace.idFromName(\"account\"));\n\t}\n\n\t// Location hints (or \"auto\"/undefined = no constraint)\n\tconst id = env.ACCOUNT.idFromName(\"account\");\n\tif (location && location !== \"auto\") {\n\t\treturn env.ACCOUNT.get(id, { locationHint: location });\n\t}\n\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 - AT Protocol standard path\napp.get(\"/xrpc/_health\", async (c) => {\n\ttry {\n\t\tconst accountDO = getAccountDO(c.env);\n\t\tawait accountDO.rpcHealthCheck();\n\t\treturn c.json({ status: \"ok\", version: `cirrus ${version}` });\n\t} catch {\n\t\treturn c.json({ status: \"unhealthy\", version: `cirrus ${version}` }, 503);\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);\napp.get(\"/xrpc/com.atproto.sync.getRecord\", (c) =>\n\tsync.getRecord(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 - handle local repo directly, proxy foreign DIDs to AppView\napp.use(\"/xrpc/com.atproto.repo.describeRepo\", async (c, next) => {\n\tconst requestedRepo = c.req.query(\"repo\");\n\tif (!requestedRepo || requestedRepo === c.env.DID) {\n\t\treturn repo.describeRepo(c, getAccountDO(c.env));\n\t}\n\tawait next();\n});\n\napp.use(\"/xrpc/com.atproto.repo.getRecord\", async (c, next) => {\n\tconst requestedRepo = c.req.query(\"repo\");\n\tif (!requestedRepo || requestedRepo === c.env.DID) {\n\t\treturn repo.getRecord(c, getAccountDO(c.env));\n\t}\n\tawait next();\n});\n\napp.use(\"/xrpc/com.atproto.repo.listRecords\", async (c, next) => {\n\tconst requestedRepo = c.req.query(\"repo\");\n\tif (!requestedRepo || requestedRepo === c.env.DID) {\n\t\treturn repo.listRecords(c, getAccountDO(c.env));\n\t}\n\tawait next();\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// Identity management for outbound migration\n// These endpoints allow migrating FROM Cirrus to another PDS\napp.post(\n\t\"/xrpc/com.atproto.identity.requestPlcOperationSignature\",\n\trequireAuth,\n\tidentity.requestPlcOperationSignature,\n);\napp.post(\n\t\"/xrpc/com.atproto.identity.signPlcOperation\",\n\trequireAuth,\n\tidentity.signPlcOperation,\n);\napp.get(\n\t\"/xrpc/gg.mk.experimental.getMigrationToken\",\n\trequireAuth,\n\tidentity.getMigrationToken,\n);\n\n// Session management\napp.post(\"/xrpc/com.atproto.server.createSession\", (c) =>\n\tserver.createSession(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.refreshSession\", (c) =>\n\tserver.refreshSession(c, getAccountDO(c.env)),\n);\napp.get(\"/xrpc/com.atproto.server.getSession\", (c) =>\n\tserver.getSession(c, getAccountDO(c.env)),\n);\napp.post(\"/xrpc/com.atproto.server.deleteSession\", server.deleteSession);\n\n// Account lifecycle\napp.get(\"/xrpc/com.atproto.server.checkAccountStatus\", requireAuth, (c) =>\n\tserver.checkAccountStatus(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);\napp.post(\n\t\"/xrpc/com.atproto.server.requestEmailUpdate\",\n\trequireAuth,\n\tserver.requestEmailUpdate,\n);\napp.post(\n\t\"/xrpc/com.atproto.server.requestEmailConfirmation\",\n\trequireAuth,\n\tserver.requestEmailConfirmation,\n);\napp.post(\"/xrpc/com.atproto.server.updateEmail\", requireAuth, (c) =>\n\tserver.updateEmail(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// Emit identity event to refresh handle verification with relays\napp.post(\n\t\"/xrpc/gg.mk.experimental.emitIdentityEvent\",\n\trequireAuth,\n\tasync (c) => {\n\t\tconst accountDO = getAccountDO(c.env);\n\t\tconst result = await accountDO.rpcEmitIdentityEvent(c.env.HANDLE);\n\t\treturn c.json(result);\n\t},\n);\n\n// Firehose status (authenticated)\napp.get(\n\t\"/xrpc/gg.mk.experimental.getFirehoseStatus\",\n\trequireAuth,\n\tasync (c) => {\n\t\tconst accountDO = getAccountDO(c.env);\n\t\treturn c.json(await accountDO.rpcGetFirehoseStatus());\n\t},\n);\n\n// ============================================\n// Passkey Routes\n// ============================================\n\n// Initialize passkey registration (authenticated)\napp.post(\"/passkey/init\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst body = await c.req\n\t\t.json<{ name?: string }>()\n\t\t.catch(() => ({}) as { name?: string });\n\ttry {\n\t\tconst result = await passkey.initPasskeyRegistration(\n\t\t\taccountDO,\n\t\t\tc.env.PDS_HOSTNAME,\n\t\t\tc.env.DID,\n\t\t\tbody.name,\n\t\t);\n\t\treturn c.json(result);\n\t} catch (err) {\n\t\tconsole.error(\"Passkey init error:\", err);\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn c.json({ error: \"PasskeyInitFailed\", message }, 500);\n\t}\n});\n\n// Passkey registration page (GET - renders UI)\napp.get(\"/passkey/register\", async (c) => {\n\tconst token = c.req.query(\"token\");\n\tif (!token) {\n\t\treturn c.html(\n\t\t\trenderPasskeyErrorPage(\n\t\t\t\t\"missing_token\",\n\t\t\t\t\"No registration token provided.\",\n\t\t\t),\n\t\t\t400,\n\t\t\t{ \"Content-Security-Policy\": PASSKEY_ERROR_CSP },\n\t\t);\n\t}\n\n\tconst accountDO = getAccountDO(c.env);\n\tconst options = await passkey.getRegistrationOptions(\n\t\taccountDO,\n\t\tc.env.PDS_HOSTNAME,\n\t\tc.env.DID,\n\t\ttoken,\n\t);\n\n\tif (!options) {\n\t\treturn c.html(\n\t\t\trenderPasskeyErrorPage(\n\t\t\t\t\"invalid_token\",\n\t\t\t\t\"Invalid or expired registration token.\",\n\t\t\t),\n\t\t\t400,\n\t\t\t{ \"Content-Security-Policy\": PASSKEY_ERROR_CSP },\n\t\t);\n\t}\n\n\tconst csp = await getPasskeyUiCsp();\n\treturn c.html(\n\t\trenderPasskeyRegistrationPage({\n\t\t\toptions,\n\t\t\ttoken,\n\t\t\thandle: c.env.HANDLE,\n\t\t}),\n\t\t200,\n\t\t{ \"Content-Security-Policy\": csp },\n\t);\n});\n\n// Complete passkey registration (POST - receives WebAuthn response)\napp.post(\"/passkey/register\", async (c) => {\n\tconst body = await c.req.json<{\n\t\ttoken: string;\n\t\tresponse: any;\n\t}>();\n\n\tif (!body.token || !body.response) {\n\t\treturn c.json({ success: false, error: \"Missing token or response\" }, 400);\n\t}\n\n\tconst accountDO = getAccountDO(c.env);\n\t// Name comes from the token (set during init)\n\tconst result = await passkey.completePasskeyRegistration(\n\t\taccountDO,\n\t\tc.env.PDS_HOSTNAME,\n\t\tbody.token,\n\t\tbody.response,\n\t);\n\n\tif (result.success) {\n\t\treturn c.json({ success: true });\n\t} else {\n\t\treturn c.json({ success: false, error: result.error }, 400);\n\t}\n});\n\n// List passkeys (authenticated)\napp.get(\"/passkey/list\", requireAuth, async (c) => {\n\tconst accountDO = getAccountDO(c.env);\n\tconst passkeys = await passkey.listPasskeys(accountDO);\n\treturn c.json({ passkeys });\n});\n\n// Delete passkey (authenticated)\napp.post(\"/passkey/delete\", requireAuth, async (c) => {\n\tconst body = await c.req.json<{ id: string }>();\n\tif (!body.id) {\n\t\treturn c.json({ success: false, error: \"Missing passkey ID\" }, 400);\n\t}\n\n\tconst accountDO = getAccountDO(c.env);\n\tconst deleted = await passkey.deletePasskey(accountDO, body.id);\n\treturn c.json({ success: deleted });\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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"}