···7171/**
7272 * Parse a post record's embed field into a PostEmbed.
7373 *
7474- * Called from `parsePostCreate` for every post event. For quote-post embed
7575- * types (record / recordWithMedia), returns null — the consumer uses
7676- * `embeddedPostUri` to detect quotes instead.
7474+ * NOT called from `parsePostCreate` — the consumer calls this directly,
7575+ * only for tracked authors, to save CPU on the firehose-scale hot path.
7676+ *
7777+ * For quote-post embed types (record / recordWithMedia), returns
7878+ * `{ skip: true }` so the consumer knows to skip the snapshot entirely.
7979+ * For images/video/external, returns `{ skip: false, embed: ... }`.
8080+ * For no embed or malformed data, returns `{ skip: false, embed: null }`.
7781 *
7882 * @param record - The raw post record object from the Jetstream commit
7983 * @param authorDid - The DID of the post's author (needed for CDN URL construction)
8080- * @returns A PostEmbed or null
8184 */
8282-export function parsePostEmbed(record: unknown, authorDid: string): PostEmbed | null {
8383- if (!isObject(record)) return null
8585+export function parsePostEmbed(
8686+ record: unknown,
8787+ authorDid: string
8888+): { skip: true } | { skip: false; embed: PostEmbed | null } {
8989+ if (!isObject(record)) return { skip: false, embed: null }
84908591 const embed = getObject(record, 'embed')
8686- if (!embed) return null
9292+ if (!embed) return { skip: false, embed: null }
87938894 const embedType = getString(embed, '$type')
8989- if (!embedType) return null
9595+ if (!embedType) return { skip: false, embed: null }
90969191- // Quote-post types — not rendered, handled by embeddedPostUri
9797+ // Quote-post types — skip the snapshot entirely
9298 if (embedType === 'app.bsky.embed.record' || embedType === 'app.bsky.embed.recordWithMedia') {
9393- return null
9999+ return { skip: true }
94100 }
9510196102 // Images embed
···98104 const rawImages = embed['images']
99105 if (!Array.isArray(rawImages) || rawImages.length === 0) {
100106 console.warn('[parsePostEmbed] images embed has no images array')
101101- return null
107107+ return { skip: false, embed: null }
102108 }
103109104110 const items: Array<{
···147153148154 if (items.length === 0) {
149155 console.warn('[parsePostEmbed] images embed produced no valid items')
150150- return null
156156+ return { skip: false, embed: null }
151157 }
152158153153- return { type: 'images', items }
159159+ return { skip: false, embed: { type: 'images', items } }
154160 }
155161156162 // Video embed
···158164 const blob = getObject(embed, 'video')
159165 if (!blob) {
160166 console.warn('[parsePostEmbed] video embed missing video blob')
161161- return null
167167+ return { skip: false, embed: null }
162168 }
163169 const cid = blobCid(blob)
164170 if (!cid) {
165171 console.warn('[parsePostEmbed] video blob missing ref.$link')
166166- return null
172172+ return { skip: false, embed: null }
167173 }
168174169175 const alt = getString(embed, 'alt') ?? ''
···187193 }
188194 }
189195190190- return result
196196+ return { skip: false, embed: result }
191197 }
192198193199 // External embed
···195201 const external = getObject(embed, 'external')
196202 if (!external) {
197203 console.warn('[parsePostEmbed] external embed missing external object')
198198- return null
204204+ return { skip: false, embed: null }
199205 }
200206201207 const uri = getString(external, 'uri')
···203209 const description = getString(external, 'description')
204210 if (uri === undefined || title === undefined || description === undefined) {
205211 console.warn('[parsePostEmbed] external embed missing uri/title/description')
206206- return null
212212+ return { skip: false, embed: null }
207213 }
208214209215 let thumb: string | null = null
···215221 }
216222 }
217223218218- return { type: 'external', uri, title, description, thumb }
224224+ return { skip: false, embed: { type: 'external', uri, title, description, thumb } }
219225 }
220226221227 // Unknown embed type — not an error, just not supported
222222- return null
228228+ return { skip: false, embed: null }
223229}
224230225231// ---------------------------------------------------------------------------
···347353348354 const postUri = `at://${did}/${collection}/${rkey}`
349355 const embeddedPostUri = extractEmbeddedPostUri(record)
350350- const embed = parsePostEmbed(record, did)
351356352357 return {
353358 kind: 'post',
···357362 text,
358363 createdAt: new Date(createdAtStr),
359364 embeddedPostUri,
360360- embed,
365365+ rawRecord: record,
361366 ingestedAt: timeUsToDate(timeUs),
362367 }
363368}
+4-3
app/lib/atproto/types.ts
···7070 */
7171 embeddedPostUri: string | null
7272 /**
7373- * Parsed embed (images/video/external link) from the post record.
7474- * Null for posts with no embed or quote-post embed types.
7373+ * The raw post record object from the Jetstream commit.
7474+ * Passed through so the consumer can call parsePostEmbed() only for
7575+ * tracked authors (saving CPU on the firehose-scale hot path).
7576 */
7676- embed: PostEmbed | null
7777+ rawRecord: Record<string, unknown>
7778 /** When the event was ingested (from top-level time_us, converted from µs) */
7879 ingestedAt: Date
7980}
+4-5
app/services/jetstream_consumer.ts
···11import type { EngagementEventRow } from '#lib/clickhouse/index'
22import type { ClickHouseStore } from '#lib/clickhouse/index'
33import type { PostSnapshot } from '#lib/clickhouse/index'
44-import { parseJetstreamEvent, parseAtUri } from '#lib/atproto/index'
44+import { parseJetstreamEvent, parseAtUri, parsePostEmbed } from '#lib/atproto/index'
55import type { PostEvent } from '#lib/atproto/index'
6677// ---------------------------------------------------------------------------
···367367368368 // Part A: snapshot insert for tracked authors
369369 if (isTrackedAuthor) {
370370- if (event.embeddedPostUri !== null) {
371371- // Quote post — skip snapshot, don't advance cursor; Part B handles engagement
372372- } else {
370370+ const embedResult = parsePostEmbed(event.rawRecord, event.authorDid)
371371+ if (!embedResult.skip) {
373372 this.snapshotBuffer.push({
374373 postUri: event.postUri,
375374 postAuthorDid: event.authorDid,
···379378 snapshotReposts: 0,
380379 snapshotQuotes: 0,
381380 snapshotTakenAt: this.deps.now(),
382382- embed: event.embed,
381381+ embed: embedResult.embed,
383382 })
384383385384 this.advancePendingCursor(timeUs)