A minimal AT Protocol Personal Data Server written in JavaScript.

style: apply biome formatting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+112 -49
+14 -5
packages/cloudflare/src/index.js
··· 645 645 async putActor(did, handle, createdAt) { 646 646 // Check for existing actor and clean up old handle mapping if changed 647 647 const existing = await this.getActor(did); 648 - if (existing?.handle && existing.handle.toLowerCase() !== handle?.toLowerCase()) { 648 + if ( 649 + existing?.handle && 650 + existing.handle.toLowerCase() !== handle?.toLowerCase() 651 + ) { 649 652 await kv.delete(`handle:${existing.handle.toLowerCase()}`); 650 653 } 651 654 // Store actor record ··· 671 674 672 675 async putOAuthRequest(requestUri, data, expiresAt) { 673 676 const ttl = Math.max(60, Math.floor((expiresAt - Date.now()) / 1000)); 674 - await kv.put(`oauth_req:${requestUri}`, JSON.stringify(data), { expirationTtl: ttl }); 677 + await kv.put(`oauth_req:${requestUri}`, JSON.stringify(data), { 678 + expirationTtl: ttl, 679 + }); 675 680 }, 676 681 677 682 async deleteOAuthRequest(requestUri) { ··· 691 696 }, 692 697 693 698 async deleteOAuthToken(tokenId) { 694 - const token = /** @type {{did: string}|null} */ (await this.getOAuthToken(tokenId)); 699 + const token = /** @type {{did: string}|null} */ ( 700 + await this.getOAuthToken(tokenId) 701 + ); 695 702 if (token?.did) { 696 703 await kv.delete(`oauth_tok_by_did:${token.did}:${tokenId}`); 697 704 } ··· 705 712 .map((k) => k.name.split(':').pop()) 706 713 .filter((/** @type {string|undefined} */ id) => id != null); 707 714 const tokens = await Promise.all( 708 - tokenIds.map((id) => this.getOAuthToken(/** @type {string} */ (id))) 715 + tokenIds.map((id) => this.getOAuthToken(/** @type {string} */ (id))), 709 716 ); 710 - return /** @type {import('@pds/core/ports').OAuthTokenData[]} */ (tokens.filter(Boolean)); 717 + return /** @type {import('@pds/core/ports').OAuthTokenData[]} */ ( 718 + tokens.filter(Boolean) 719 + ); 711 720 }, 712 721 713 722 async checkAndStoreDpopJti(jti, expiresAt) {
+13 -3
packages/core/src/pds.js
··· 891 891 } 892 892 893 893 const _did = await this.getDid(); 894 - const result = await this.actorStorage.listRecords(collection, cursor, limit); 894 + const result = await this.actorStorage.listRecords( 895 + collection, 896 + cursor, 897 + limit, 898 + ); 895 899 896 900 const records = result.records.map((r) => ({ 897 901 uri: r.uri, ··· 1237 1241 await this.createCommit(did, [{ action: 'delete', uri }]); 1238 1242 1239 1243 // Clean up orphaned blobs immediately (threshold = future means all orphaned blobs qualify) 1240 - const orphanedCids = await this.actorStorage.getOrphanedBlobs(Date.now() + 1000); 1244 + const orphanedCids = await this.actorStorage.getOrphanedBlobs( 1245 + Date.now() + 1000, 1246 + ); 1241 1247 for (const cid of orphanedCids) { 1242 1248 try { 1243 1249 await this.blobs.delete(did, cid); ··· 2841 2847 } 2842 2848 2843 2849 // Register actor in shared storage for handle resolution 2844 - await this.sharedStorage.putActor(body.did, body.handle || null, Date.now()); 2850 + await this.sharedStorage.putActor( 2851 + body.did, 2852 + body.handle || null, 2853 + Date.now(), 2854 + ); 2845 2855 2846 2856 // Clear cached values 2847 2857 this._did = null;
+1 -4
packages/core/test/pds.test.js
··· 1 1 import { describe, expect, test } from 'vitest'; 2 - import { 3 - getKnownServiceUrl, 4 - parseAtprotoProxyHeader, 5 - } from '@pds/core/pds'; 2 + import { getKnownServiceUrl, parseAtprotoProxyHeader } from '@pds/core/pds'; 6 3 7 4 // Internal constant - not exported from pds.js due to Cloudflare Workers limitation 8 5 const BSKY_APPVIEW_URL = 'https://api.bsky.app';
+10 -4
packages/deno/src/index.js
··· 133 133 */ 134 134 listen() { 135 135 return new Promise((resolve) => { 136 - server = Deno.serve({ port, onListen: () => { 137 - console.log(`PDS listening on http://localhost:${port}`); 138 - resolve(server); 139 - }}, (request) => pds.fetch(request)); 136 + server = Deno.serve( 137 + { 138 + port, 139 + onListen: () => { 140 + console.log(`PDS listening on http://localhost:${port}`); 141 + resolve(server); 142 + }, 143 + }, 144 + (request) => pds.fetch(request), 145 + ); 140 146 141 147 // Start cleanup interval (every hour) 142 148 cleanupInterval = setInterval(
+48 -16
packages/storage-sqlite/src/index.js
··· 348 348 `); 349 349 350 350 const stmts = { 351 - getActor: db.prepare('SELECT did, handle, created_at FROM actors WHERE did = ?'), 352 - resolveHandle: db.prepare('SELECT did FROM actors WHERE lower(handle) = lower(?)'), 353 - putActor: db.prepare('INSERT OR REPLACE INTO actors (did, handle, created_at) VALUES (?, ?, ?)'), 351 + getActor: db.prepare( 352 + 'SELECT did, handle, created_at FROM actors WHERE did = ?', 353 + ), 354 + resolveHandle: db.prepare( 355 + 'SELECT did FROM actors WHERE lower(handle) = lower(?)', 356 + ), 357 + putActor: db.prepare( 358 + 'INSERT OR REPLACE INTO actors (did, handle, created_at) VALUES (?, ?, ?)', 359 + ), 354 360 deleteActor: db.prepare('DELETE FROM actors WHERE did = ?'), 355 361 356 - getOAuthRequest: db.prepare('SELECT data, expires_at FROM oauth_requests WHERE request_uri = ?'), 357 - putOAuthRequest: db.prepare('INSERT OR REPLACE INTO oauth_requests (request_uri, data, expires_at) VALUES (?, ?, ?)'), 358 - deleteOAuthRequest: db.prepare('DELETE FROM oauth_requests WHERE request_uri = ?'), 362 + getOAuthRequest: db.prepare( 363 + 'SELECT data, expires_at FROM oauth_requests WHERE request_uri = ?', 364 + ), 365 + putOAuthRequest: db.prepare( 366 + 'INSERT OR REPLACE INTO oauth_requests (request_uri, data, expires_at) VALUES (?, ?, ?)', 367 + ), 368 + deleteOAuthRequest: db.prepare( 369 + 'DELETE FROM oauth_requests WHERE request_uri = ?', 370 + ), 359 371 360 - getOAuthToken: db.prepare('SELECT data FROM oauth_tokens WHERE token_id = ?'), 361 - putOAuthToken: db.prepare('INSERT OR REPLACE INTO oauth_tokens (token_id, did, data) VALUES (?, ?, ?)'), 372 + getOAuthToken: db.prepare( 373 + 'SELECT data FROM oauth_tokens WHERE token_id = ?', 374 + ), 375 + putOAuthToken: db.prepare( 376 + 'INSERT OR REPLACE INTO oauth_tokens (token_id, did, data) VALUES (?, ?, ?)', 377 + ), 362 378 deleteOAuthToken: db.prepare('DELETE FROM oauth_tokens WHERE token_id = ?'), 363 - listOAuthTokensByDid: db.prepare('SELECT data FROM oauth_tokens WHERE did = ?'), 379 + listOAuthTokensByDid: db.prepare( 380 + 'SELECT data FROM oauth_tokens WHERE did = ?', 381 + ), 364 382 365 - storeDpopJti: db.prepare('INSERT OR IGNORE INTO dpop_jtis (jti, expires_at) VALUES (?, ?)'), 366 - cleanupExpiredDpopJtis: db.prepare('DELETE FROM dpop_jtis WHERE expires_at < ?'), 383 + storeDpopJti: db.prepare( 384 + 'INSERT OR IGNORE INTO dpop_jtis (jti, expires_at) VALUES (?, ?)', 385 + ), 386 + cleanupExpiredDpopJtis: db.prepare( 387 + 'DELETE FROM dpop_jtis WHERE expires_at < ?', 388 + ), 367 389 }; 368 390 369 391 return { 370 392 async getActor(did) { 371 393 const row = /** @type {ActorRow|undefined} */ (stmts.getActor.get(did)); 372 - return row ? { did: row.did, handle: row.handle, createdAt: row.created_at } : null; 394 + return row 395 + ? { did: row.did, handle: row.handle, createdAt: row.created_at } 396 + : null; 373 397 }, 374 398 375 399 async resolveHandle(handle) { 376 - const row = /** @type {{did: string}|undefined} */ (stmts.resolveHandle.get(handle)); 400 + const row = /** @type {{did: string}|undefined} */ ( 401 + stmts.resolveHandle.get(handle) 402 + ); 377 403 return row ? row.did : null; 378 404 }, 379 405 ··· 386 412 }, 387 413 388 414 async getOAuthRequest(requestUri) { 389 - const row = /** @type {OAuthRequestRow|undefined} */ (stmts.getOAuthRequest.get(requestUri)); 415 + const row = /** @type {OAuthRequestRow|undefined} */ ( 416 + stmts.getOAuthRequest.get(requestUri) 417 + ); 390 418 if (!row) return null; 391 419 if (row.expires_at < Date.now()) { 392 420 stmts.deleteOAuthRequest.run(requestUri); ··· 409 437 }, 410 438 411 439 async getOAuthToken(tokenId) { 412 - const row = /** @type {OAuthTokenRow|undefined} */ (stmts.getOAuthToken.get(tokenId)); 440 + const row = /** @type {OAuthTokenRow|undefined} */ ( 441 + stmts.getOAuthToken.get(tokenId) 442 + ); 413 443 if (!row) return null; 414 444 try { 415 445 return JSON.parse(row.data); ··· 429 459 }, 430 460 431 461 async listOAuthTokensByDid(did) { 432 - const rows = /** @type {OAuthTokenRow[]} */ (stmts.listOAuthTokensByDid.all(did)); 462 + const rows = /** @type {OAuthTokenRow[]} */ ( 463 + stmts.listOAuthTokensByDid.all(did) 464 + ); 433 465 return rows 434 466 .map((r) => { 435 467 try {
+1 -4
test/e2e.test.js
··· 13 13 stopNodeServer, 14 14 USE_LOCAL_INFRA, 15 15 } from './helpers/node-server.js'; 16 - import { 17 - startDenoServer, 18 - stopDenoServer, 19 - } from './helpers/deno-server.js'; 16 + import { startDenoServer, stopDenoServer } from './helpers/deno-server.js'; 20 17 import { getOAuthTokenWithScope, setBaseUrl } from './helpers/oauth.js'; 21 18 import { ensureDockerServices, RELAY_URL } from './helpers/docker-services.js'; 22 19 import { createTestIdentity } from './helpers/identity.js';
+10 -4
test/helpers/node-server.js
··· 55 55 56 56 async put(did, cid, data, mimeType) { 57 57 const objectName = `${did}/${cid}`; 58 - await client.putObject(MINIO_BUCKET, objectName, Buffer.from(data), data.length, { 59 - 'Content-Type': mimeType, 60 - mime_type: mimeType, 61 - }); 58 + await client.putObject( 59 + MINIO_BUCKET, 60 + objectName, 61 + Buffer.from(data), 62 + data.length, 63 + { 64 + 'Content-Type': mimeType, 65 + mime_type: mimeType, 66 + }, 67 + ); 62 68 }, 63 69 64 70 async delete(did, cid) {
+7 -1
tsconfig.build.json
··· 6 6 "emitDeclarationOnly": true 7 7 }, 8 8 "include": ["packages/*/src/**/*.js"], 9 - "exclude": ["node_modules", "test", "examples", "packages/deno", "packages/blobs-deno"] 9 + "exclude": [ 10 + "node_modules", 11 + "test", 12 + "examples", 13 + "packages/deno", 14 + "packages/blobs-deno" 15 + ] 10 16 }
+7 -4
tsconfig.json
··· 14 14 "paths": { 15 15 "@pds/core": ["packages/core/src/index.js"], 16 16 "@pds/core/*": ["packages/core/src/*"], 17 - "@pds/storage-sqlite": [ 18 - "packages/storage-sqlite/src/index.js" 19 - ], 17 + "@pds/storage-sqlite": ["packages/storage-sqlite/src/index.js"], 20 18 "@pds/blobs-s3": ["packages/blobs-s3/src/index.js"], 21 19 "@pds/blobs-fs": ["packages/blobs-fs/src/index.js"], 22 20 "@pds/node": ["packages/node/src/index.js"], ··· 24 22 } 25 23 }, 26 24 "include": ["src/**/*.js", "packages/**/*.js", "test/**/*.js"], 27 - "exclude": ["node_modules", "packages/deno", "packages/blobs-deno", "examples/deno"] 25 + "exclude": [ 26 + "node_modules", 27 + "packages/deno", 28 + "packages/blobs-deno", 29 + "examples/deno" 30 + ] 28 31 }
+1 -4
vitest.config.js
··· 2 2 3 3 export default defineConfig({ 4 4 test: { 5 - include: [ 6 - 'packages/*/test/**/*.test.js', 7 - 'test/**/*.test.js' 8 - ], 5 + include: ['packages/*/test/**/*.test.js', 'test/**/*.test.js'], 9 6 testTimeout: 30000, 10 7 hookTimeout: 60000, 11 8 coverage: {