A TypeScript toolkit for consuming the Bluesky network in real-time.
1import { z } from "zod";
2
3// --- Jetstream Event Schemas ---
4
5export const CommitOperationSchema = z.enum(["create", "update", "delete"]);
6
7const CommitBaseSchema = z.object({
8 rev: z.string(),
9 operation: CommitOperationSchema,
10 collection: z.string(),
11 rkey: z.string(),
12});
13
14export const CommitCreatePayloadSchema = CommitBaseSchema.extend({
15 operation: z.literal("create"),
16 record: z.any(),
17 cid: z.string(),
18});
19
20export const CommitUpdatePayloadSchema = CommitBaseSchema.extend({
21 operation: z.literal("update"),
22 record: z.any(),
23 cid: z.string(),
24});
25
26export const CommitDeletePayloadSchema = CommitBaseSchema.extend({
27 operation: z.literal("delete"),
28});
29
30export const CommitPayloadSchema = z.discriminatedUnion("operation", [
31 CommitCreatePayloadSchema,
32 CommitUpdatePayloadSchema,
33 CommitDeletePayloadSchema,
34]);
35
36const JetstreamEventBaseSchema = z.object({
37 did: z.string(),
38 time_us: z.number(),
39});
40
41export const JetstreamCommitEventSchema = JetstreamEventBaseSchema.extend({
42 kind: z.literal("commit"),
43 commit: CommitPayloadSchema,
44});
45
46export const IdentityPayloadSchema = z.object({
47 did: z.string(),
48 handle: z.string(),
49 seq: z.number(),
50 time: z.string(),
51});
52
53export const JetstreamIdentityEventSchema = JetstreamEventBaseSchema.extend({
54 kind: z.literal("identity"),
55 identity: IdentityPayloadSchema,
56});
57
58export const AccountStatusSchema = z.enum([
59 "active",
60 "deactivated",
61 "suspended",
62 "deleted",
63 "takendown",
64]);
65
66export const AccountPayloadSchema = z.object({
67 active: z.boolean(),
68 did: z.string(),
69 seq: z.number(),
70 time: z.string(),
71 status: AccountStatusSchema.optional(),
72});
73
74export const JetstreamAccountEventSchema = JetstreamEventBaseSchema.extend({
75 kind: z.literal("account"),
76 account: AccountPayloadSchema,
77});
78
79export const JetstreamEventSchema = z.discriminatedUnion("kind", [
80 JetstreamCommitEventSchema,
81 JetstreamIdentityEventSchema,
82 JetstreamAccountEventSchema,
83]);
84
85// --- Inferred types ---
86
87export type CommitOperation = z.infer<typeof CommitOperationSchema>;
88export type CommitBase = z.infer<typeof CommitBaseSchema>;
89export type CommitCreatePayload = z.infer<typeof CommitCreatePayloadSchema>;
90export type CommitUpdatePayload = z.infer<typeof CommitUpdatePayloadSchema>;
91export type CommitDeletePayload = z.infer<typeof CommitDeletePayloadSchema>;
92export type CommitPayload = z.infer<typeof CommitPayloadSchema>;
93export type JetstreamEventBase = z.infer<typeof JetstreamEventBaseSchema>;
94export type JetstreamCommitEvent = z.infer<typeof JetstreamCommitEventSchema>;
95export type IdentityPayload = z.infer<typeof IdentityPayloadSchema>;
96export type JetstreamIdentityEvent = z.infer<typeof JetstreamIdentityEventSchema>;
97export type AccountStatus = z.infer<typeof AccountStatusSchema>;
98export type AccountPayload = z.infer<typeof AccountPayloadSchema>;
99export type JetstreamAccountEvent = z.infer<typeof JetstreamAccountEventSchema>;
100export type JetstreamEvent = z.infer<typeof JetstreamEventSchema>;
101
102// --- Post Record Schema ---
103
104export const PostRecordSchema = z.object({
105 text: z.string().min(1),
106 langs: z.array(z.string()).optional().default([]),
107 createdAt: z.string(),
108 facets: z.array(z.any()).optional(),
109 embed: z.object({ $type: z.string() }).passthrough().optional(),
110});
111
112export type PostRecord = z.infer<typeof PostRecordSchema>;
113
114// --- Post Types ---
115
116export const PostDataSchema = z.object({
117 did: z.string(),
118 text: z.string(),
119 langs: z.array(z.string()),
120 links: z.array(z.string()),
121 mentionCount: z.number(),
122 embedType: z.string().nullable(),
123 createdAt: z.string(),
124 uri: z.string(),
125 rkey: z.string(),
126});
127
128export type PostData = z.infer<typeof PostDataSchema>;
129
130export const PostUpdateDataSchema = PostDataSchema.extend({
131 cid: z.string(),
132});
133
134export type PostUpdateData = z.infer<typeof PostUpdateDataSchema>;
135
136export const PostDeleteDataSchema = z.object({
137 did: z.string(),
138 collection: z.string(),
139 rkey: z.string(),
140 uri: z.string(),
141});
142
143export type PostDeleteData = z.infer<typeof PostDeleteDataSchema>;
144
145export const KeywordPostSchema = PostDataSchema.extend({
146 matchedKeywords: z.array(z.string()),
147});
148
149export type KeywordPost = z.infer<typeof KeywordPostSchema>;
150
151// --- User Types ---
152
153export const UserSchema = z.object({
154 displayName: z.string(),
155 handle: z.string(),
156});
157
158export type User = z.infer<typeof UserSchema>;
159
160export type UserRegistry = ReadonlyMap<string, User>;
161
162export const UserPostSchema = PostDataSchema.extend({
163 displayName: z.string(),
164 handle: z.string(),
165});
166
167export type UserPost = z.infer<typeof UserPostSchema>;
168
169// --- Config ---
170
171export const JetstreamConfigSchema = z.object({
172 endpoint: z.string().optional(),
173 wantedCollections: z.array(z.string()).readonly().optional(),
174 wantedDids: z.array(z.string()).readonly().optional(),
175 requireHello: z.boolean().optional(),
176 cursor: z.number().optional(),
177 compress: z.boolean().optional(),
178 maxMessageSizeBytes: z.number().optional(),
179});
180
181export type JetstreamConfig = z.infer<typeof JetstreamConfigSchema>;
182
183// --- API Option Types ---
184
185export const SearchPostsOptionsSchema = z.object({
186 sort: z.enum(["top", "latest"]).optional(),
187 since: z.string().optional(),
188 until: z.string().optional(),
189 mentions: z.string().optional(),
190 author: z.string().optional(),
191 lang: z.string().optional(),
192 domain: z.string().optional(),
193 url: z.string().optional(),
194 tag: z.array(z.string()).readonly().optional(),
195 limit: z.number().optional(),
196 cursor: z.string().optional(),
197});
198
199export type SearchPostsOptions = z.infer<typeof SearchPostsOptionsSchema>;
200
201export const GetAuthorFeedOptionsSchema = z.object({
202 filter: z.enum([
203 "posts_with_replies",
204 "posts_no_replies",
205 "posts_with_media",
206 "posts_and_author_threads",
207 ]).optional(),
208 limit: z.number().optional(),
209 cursor: z.string().optional(),
210 includePins: z.boolean().optional(),
211});
212
213export type GetAuthorFeedOptions = z.infer<typeof GetAuthorFeedOptionsSchema>;
214
215export const PaginationOptionsSchema = z.object({
216 limit: z.number().optional(),
217 cursor: z.string().optional(),
218});
219
220export type PaginationOptions = z.infer<typeof PaginationOptionsSchema>;
221
222// --- PostView Types (API response shapes) ---
223
224export const PostViewSchema = z.object({
225 uri: z.string(),
226 author: z.object({
227 did: z.string(),
228 handle: z.string(),
229 displayName: z.string().optional(),
230 }).passthrough(),
231 record: z.object({
232 text: z.string(),
233 }).passthrough(),
234 likeCount: z.number().optional(),
235 repostCount: z.number().optional(),
236 replyCount: z.number().optional(),
237 quoteCount: z.number().optional(),
238}).passthrough();
239
240export type PostView = z.infer<typeof PostViewSchema>;
241
242export const PostViewInfoSchema = z.object({
243 text: z.string(),
244 displayName: z.string(),
245 handle: z.string(),
246 did: z.string(),
247 rkey: z.string(),
248 bskyUrl: z.string(),
249 uri: z.string(),
250});
251
252export type PostViewInfo = z.infer<typeof PostViewInfoSchema>;