A TypeScript toolkit for consuming the Bluesky network in real-time.
1// Post Lifecycle Tracker
2// Streams post events from the Bluesky network, focusing on the interesting
3// ones: edits and deletes. Creates are counted silently and shown in a
4// periodic stats line. Use --all to print every create too.
5// Demonstrates: parsePost, parsePostUpdate, parsePostDelete, toBskyUrl.
6//
7// Usage: npx tsx examples/post-lifecycle.ts [options]
8// Options:
9// --all Print creates, updates, and deletes
10// --creates-only Print only creates
11// --updates-only Print only updates
12// --deletes-only Print only deletes
13
14import {
15 startStreamWithReconnect,
16 parsePost,
17 parsePostUpdate,
18 parsePostDelete,
19 toBskyUrl,
20 formatTruncated,
21} from "../lib/index.js";
22import type { PostData, PostUpdateData, PostDeleteData } from "../lib/index.js";
23
24// --- Config ---
25
26const args = new Set(process.argv.slice(2));
27
28const printCreates = args.has("--all") || args.has("--creates-only");
29const printUpdates = !args.has("--creates-only") && !args.has("--deletes-only");
30const printDeletes = !args.has("--creates-only") && !args.has("--updates-only");
31
32const STATS_INTERVAL_MS = 10_000;
33
34// --- Counters ---
35
36let creates = 0;
37let updates = 0;
38let deletes = 0;
39
40const stats = () => `[C:${creates} U:${updates} D:${deletes}]`;
41
42// --- Formatters ---
43
44const formatCreate = (post: PostData): string => {
45 const text = formatTruncated(post.text, 100);
46 const url = toBskyUrl(post.did, post.rkey);
47 return [
48 `+ CREATE ${stats()}`,
49 ` ${text}`,
50 ` ${url}`,
51 ].join("\n");
52};
53
54const formatUpdate = (post: PostUpdateData): string => {
55 const text = formatTruncated(post.text, 100);
56 const url = toBskyUrl(post.did, post.rkey);
57 return [
58 `~ UPDATE ${stats()}`,
59 ` ${text}`,
60 ` cid: ${post.cid}`,
61 ` ${url}`,
62 ].join("\n");
63};
64
65const formatDelete = (post: PostDeleteData): string => {
66 return [
67 `- DELETE ${stats()}`,
68 ` ${post.uri}`,
69 ` did: ${post.did}`,
70 ].join("\n");
71};
72
73// --- Stats timer ---
74
75let lastStats = { creates: 0, updates: 0, deletes: 0 };
76
77const printStats = () => {
78 const dc = creates - lastStats.creates;
79 const du = updates - lastStats.updates;
80 const dd = deletes - lastStats.deletes;
81 const elapsed = STATS_INTERVAL_MS / 1000;
82 console.log(
83 ` --- ${stats()} | last ${elapsed}s: +${dc} creates, ~${du} updates, -${dd} deletes (${(dc / elapsed).toFixed(0)} posts/sec) ---\n`
84 );
85 lastStats = { creates, updates, deletes };
86};
87
88// --- Stream ---
89
90startStreamWithReconnect({
91 config: { wantedCollections: ["app.bsky.feed.post"] },
92 onEvent: (event) => {
93 const created = parsePost(event);
94 if (created) {
95 creates++;
96 if (printCreates) console.log(formatCreate(created) + "\n");
97 return;
98 }
99
100 const updated = parsePostUpdate(event);
101 if (updated) {
102 updates++;
103 if (printUpdates) console.log(formatUpdate(updated) + "\n");
104 return;
105 }
106
107 const deleted = parsePostDelete(event);
108 if (deleted) {
109 deletes++;
110 if (printDeletes) console.log(formatDelete(deleted) + "\n");
111 }
112 },
113 onOpen: () => {
114 const showing = [
115 printCreates && "creates",
116 printUpdates && "updates",
117 printDeletes && "deletes",
118 ].filter(Boolean).join(", ");
119 console.log(`Connected. Printing: ${showing}. Stats every ${STATS_INTERVAL_MS / 1000}s.\n`);
120 },
121});
122
123setInterval(printStats, STATS_INTERVAL_MS);