Wraps the createSession + Authorization: Bearer flow so callers don't
need to assemble curl by hand. Reads BSKY_IDENTIFIER / BSKY_PASSWORD /
BSKY_PDS / REMANSO_API from env (or matching flags). Defaults to
bsky.social so non-bsky-hosted accounts must set BSKY_PDS explicitly,
e.g. BSKY_PDS=https://eurosky.social.
Both POST /:did/webhooks and DELETE /:did/webhooks were unauthenticated:
anyone could register a webhook for someone else's DID (privacy leak)
or wipe a DID's webhook list (DoS on legitimate subscribers). Now both
endpoints require a Bluesky session bearer token, verified end-to-end
against the DID's PDS via the existing authenticateRequest helper, and
the verified DID must match the URL :did.
Subscriptions now declare a `verb` (create | delete | bulk-create).
POST /:did/webhooks defaults to inserting both create and delete rows
when no verb is given, preserving existing all-events behavior. Update
events fold into the create verb. The new bulk-create verb debounces
creates per DID over 400 ms and delivers a `records` array.
Migration adds the verb column with default 'create' and clones every
existing row for the delete verb so legacy subscriptions keep firing
on both events.
Set busy_timeout before journal_mode=WAL in db.ts so SQLite retries
for 10s instead of failing immediately with the default 0ms timeout.
Extract migration into a dedicated Docker Compose service so both
jetstream and api wait for it to complete before opening the database.
Pure rename — no behavioral change. "discoverable" more clearly
communicates that the field controls whether a note can be found
by others in public listings.
Notes with listed=false are filtered out from all GET /notes queries.
The field defaults to true so existing and new notes without it remain visible.
Migration handles existing databases with ALTER TABLE ADD COLUMN.
- db.ts: getWebhooksByDid returns the 10 most recent subscriptions (ORDER BY id DESC LIMIT 10)
- jetstream.ts: fireWebhooks fans out to registered URLs via Promise.allSettled after each create/update/delete event
- Migration: CREATE TABLE webhook_subscription (id, did, method, url) with index on did
- db.ts: addWebhookSubscription and deleteWebhooksByDid helpers
- server.ts: POST /:did/webhooks (201) and DELETE /:did/webhooks (204)
Jetstream was running backgrounded in the same container as the API server,
so crashes went undetected and Docker never restarted it. Now each process
runs as a separate docker-compose service with independent restart policies.
Also adds cursor persistence to SQLite (saved every 5s) so restarts resume
from where they left off, moves event destructuring inside try/catch blocks,
and adds global unhandled error/rejection handlers for crash visibility.
Verify the caller owns the DID by resolving their PDS via plc.directory
and validating the session token before allowing note deletion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>