an app to share curated trails
sidetrail.app
1import "./dotenv.js"; // Must be first - loads .env before other imports
2import { getCursor, setCursor, db } from "./db.js";
3import { Jetstream } from "./jetstream.js";
4import { handleEvent, COLLECTIONS } from "./handler.js";
5
6const JETSTREAM_URL =
7 process.env.JETSTREAM_URL || "wss://jetstream2.us-east.bsky.network/subscribe";
8
9async function main() {
10 console.log("Sidetrail Ingester starting...");
11
12 if (!process.env.DATABASE_URL) {
13 console.error("ERROR: DATABASE_URL environment variable is required");
14 process.exit(1);
15 }
16
17 // Get cursor for resumable ingestion
18 const cursor = await getCursor();
19 console.log(`Starting from cursor: ${cursor ?? "beginning"}`);
20
21 // Create Jetstream connection
22 const jetstream = new Jetstream({
23 instanceUrl: JETSTREAM_URL,
24 wantedCollections: COLLECTIONS,
25 cursor,
26 setCursor,
27 onEvent: async (evt) => {
28 const { commit } = evt as { commit?: { collection: string; operation: string } };
29 if (commit) {
30 console.log(`${commit.operation.toUpperCase()} ${commit.collection}`);
31 }
32 await handleEvent(db, evt);
33 },
34 onError: (err) => {
35 console.error("Jetstream error:", err);
36 },
37 });
38
39 // Handle graceful shutdown
40 process.on("SIGINT", () => {
41 console.log("Shutting down...");
42 jetstream.destroy();
43 process.exit(0);
44 });
45
46 process.on("SIGTERM", () => {
47 console.log("Shutting down...");
48 jetstream.destroy();
49 process.exit(0);
50 });
51
52 // Start listening
53 jetstream.start();
54 console.log(`Listening for: ${COLLECTIONS.join(", ")}`);
55}
56
57main().catch((err) => {
58 console.error("Fatal error:", err);
59 process.exit(1);
60});