chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
@pdsjs/blobs-s3
2.5.2
2.5.1
2.5.0
Minor Changes
-
078c2df: The S3 backup target sends an object past 8 MiB as a multipart upload, and accepts a blob as a stream. A single PUT carrying a whole blob is what a provider answers HTTP 413 to, and one such blob failed the entire snapshot: the blob loop runs before the repo CAR and the manifest, so the run wrote nothing at all. Reading the blob whole also put a video-sized buffer in an isolate that has 128 MB.
A backup now streams each blob from the store to the target one part at a time, so neither side ever holds more than a part.
BackupTargetPortgains an optionalputStream, and a run counts the bytes as they pass, which keeps its heartbeat beating across the parts of one large blob.
2.4.3
2.4.2
2.4.1
2.4.0
Patch Changes
- 6b79149: Video-ready blob hosting. Blob uploads and downloads stream:
uploadBlobhashes and stages bytes chunk by chunk instead of buffering the body, andgetBlobanswers byte ranges from a streamed read, so a 300 MB video fits through a Durable Object and seeks in a browser. The blob port gains optionalcreateWriterandgetStream; the fs, R2, S3 and Deno stores implement both, and each platform injects its native SHA-256. Drive stores a file as one blob of its own type when the store streams, takes large files across several requests through upload sessions, and offers a Link dialog with the blob ref any record can embed. The Worker serves public blobs through the edge cache, and a site's/.blobs/route serves ranges and now applies the same space-privacy filter asgetBlob. A newvideos.mjsexample app is a gallery over the drive's videos. The node server accepts ablobUploadLimitoption (PDS_BLOB_UPLOAD_LIMITin the example), which Bluesky video posting needs.
2.3.0
2.2.0
Minor Changes
-
224cb48:
createS3FetchBackupTarget: back up to any S3-compatible bucket over plain fetch and WebCrypto — no AWS SDK, so it runs on Cloudflare Workers, Node, and Deno alike. Signature Version 4 with path-style addressing and the four basic operations, which is the subset every compatible provider (Bunny, Backblaze B2, MinIO, R2, AWS) implements; the signer is verified against AWS's own documented example vectors.On Cloudflare this unlocks off-provider backups: set
BACKUP_S3_ENDPOINT,BACKUP_S3_BUCKET,BACKUP_S3_REGIONas vars and the two keys as secrets, and the target wins over the same-accountBACKUPSR2 binding. The Node example now uses it forPDS_BACKUP_S3_*too, dropping the@aws-sdk/client-s3requirement. -
224cb48: Scheduled backups, with a Backups page in the account UI.
A backup is a snapshot in a new
BackupTargetPort— a directory (createFsBackupTarget), an S3-compatible bucket (createS3BackupTarget), or on Cloudflare an R2 bucket bound asBACKUPS. Each snapshot holds the full signed repo CAR (the same bytescom.atproto.sync.getReposerves) and a manifest, written last so an interrupted snapshot never counts; blobs are stored content-addressed atblobs/<cid>and shared across snapshots, so a run only uploads what the target doesn't already hold. Retention keeps the newest N snapshots and reaps blobs no kept manifest references.Scheduling rides the maintenance tick each runtime already had: the hourly interval on Node and Deno, and the Durable Object alarm on Cloudflare, which now fires hourly to match. Settings and run history live in the existing
metadatatable as one JSON document, read through new optionalgetBackupState/setBackupStatemethods onActorStoragePort.The signing key stays out of snapshots unless opted in: entering a passphrase on the Backups page seals a copy under PBKDF2 → AES-256-GCM (
key.encin each snapshot), and the page warns when the key has rotated since it was sealed. The page also offers one-click manual runs, run history, schedule and retention settings, and a direct.cardownload that works even with no target configured.