···11+import { appBskyRichtextFacetSchema } from "@/lib/types/lexicons/app/bsky/richtext/facet";
22+import { comAtprotoLabelDefsSelfLabelSchema } from "@/lib/types/lexicons/com/atproto/label/defs";
33+import { comAtprotoRepoStrongRefSchema } from "@/lib/types/lexicons/com/atproto/repo/strongRef";
44+import { z } from "zod/v4";
55+66+/** Embed types (union members) – stubbed as loose objects keyed by $type */
77+const embedImages = z
88+ .object({ $type: z.literal("app.bsky.embed.images") })
99+ .loose()
1010+ .describe("app.bsky.embed.images");
1111+1212+const embedVideo = z
1313+ .object({ $type: z.literal("app.bsky.embed.video") })
1414+ .loose()
1515+ .describe("app.bsky.embed.video");
1616+1717+const embedExternal = z
1818+ .object({ $type: z.literal("app.bsky.embed.external") })
1919+ .loose()
2020+ .describe("app.bsky.embed.external");
2121+2222+const embedRecord = z
2323+ .object({ $type: z.literal("app.bsky.embed.record") })
2424+ .loose()
2525+ .describe("app.bsky.embed.record");
2626+2727+const embedRecordWithMedia = z
2828+ .object({ $type: z.literal("app.bsky.embed.recordWithMedia") })
2929+ .loose()
3030+ .describe("app.bsky.embed.recordWithMedia");
3131+3232+const embed = z.discriminatedUnion("$type", [
3333+ embedImages,
3434+ embedVideo,
3535+ embedExternal,
3636+ embedRecord,
3737+ embedRecordWithMedia,
3838+]);
3939+4040+const replyRef = z.object({
4141+ root: comAtprotoRepoStrongRefSchema,
4242+ parent: comAtprotoRepoStrongRefSchema,
4343+});
4444+4545+const labels = z
4646+ .discriminatedUnion("$type", [comAtprotoLabelDefsSelfLabelSchema])
4747+ .describe("Self-label values for this post. Effectively content warnings.");
4848+4949+const textSlice = z
5050+ .object({
5151+ start: z.int().min(0),
5252+ end: z.int().min(0),
5353+ })
5454+ .describe(
5555+ "Deprecated. Use app.bsky.richtext instead – A text segment. " +
5656+ "Start is inclusive, end is exclusive. Indices are for utf16-encoded strings.",
5757+ );
5858+5959+const entity = z
6060+ .object({
6161+ index: textSlice,
6262+ type: z.string().describe("Expected values are 'mention' and 'link'."),
6363+ value: z.string(),
6464+ })
6565+ .describe("Deprecated: use facets instead.");
6666+6767+export const appBskyFeedPostSchema = z
6868+ .object({
6969+ text: z
7070+ .string()
7171+ .max(3000)
7272+ .describe(
7373+ "The primary post content. May be an empty string, if there are embeds.",
7474+ ),
7575+ entities: z
7676+ .array(entity)
7777+ .optional()
7878+ .describe("DEPRECATED: replaced by app.bsky.richtext.facet."),
7979+ facets: z
8080+ .array(appBskyRichtextFacetSchema)
8181+ .optional()
8282+ .describe("Annotations of text (mentions, URLs, hashtags, etc)"),
8383+ reply: replyRef.optional(),
8484+ embed: embed.optional(),
8585+ langs: z
8686+ .array(z.string().describe("BCP-47 language tag"))
8787+ .max(3)
8888+ .optional()
8989+ .describe("Indicates human language of post primary text content."),
9090+ labels: labels.optional(),
9191+ tags: z
9292+ .array(z.string().max(640))
9393+ .max(8)
9494+ .optional()
9595+ .describe(
9696+ "Additional hashtags, in addition to any included in post text and facets.",
9797+ ),
9898+ createdAt: z.iso
9999+ .datetime()
100100+ .describe(
101101+ "Client-declared timestamp when this post was originally created.",
102102+ ),
103103+ })
104104+ .describe("Record containing a Bluesky post.");
105105+106106+export type Post = z.infer<typeof appBskyFeedPostSchema>;
+88
src/lib/types/lexicons/app/bsky/richtext/facet.ts
···11+// I'm not gonna lie. I let Claude (Opus 4.6) handle this. I genuinely cannot be arsed, at least until I finish Anaxagoras.
22+33+import { z } from "zod/v4";
44+55+// ---------------------------------------------------------------------------
66+// #byteSlice
77+// ---------------------------------------------------------------------------
88+99+/**
1010+ * Specifies the sub-string range a facet feature applies to.
1111+ * Start index is inclusive, end index is exclusive. Indices are zero-indexed,
1212+ * counting bytes of the UTF-8 encoded text.
1313+ *
1414+ * NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for
1515+ * string slice indexing; in these languages, convert to byte arrays before
1616+ * working with facets.
1717+ */
1818+export const appBskyRichtextFacetByteSliceSchema = z
1919+ .object({
2020+ byteStart: z.int().min(0),
2121+ byteEnd: z.int().min(0),
2222+ })
2323+ .describe(
2424+ "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.",
2525+ );
2626+2727+export type AppBskyRichtextFacetByteSlice = z.infer<
2828+ typeof appBskyRichtextFacetByteSliceSchema
2929+>;
3030+3131+export const appBskyRichtextFacetMentionSchema = z
3232+ .object({
3333+ $type: z.literal("app.bsky.richtext.facet#mention"),
3434+ did: z.string().describe("DID of the mentioned account."),
3535+ })
3636+ .describe(
3737+ "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.",
3838+ );
3939+4040+export type AppBskyRichtextFacetMention = z.infer<
4141+ typeof appBskyRichtextFacetMentionSchema
4242+>;
4343+4444+export const appBskyRichtextFacetLinkSchema = z
4545+ .object({
4646+ $type: z.literal("app.bsky.richtext.facet#link"),
4747+ uri: z.url().describe("Complete URL the facet refers to."),
4848+ })
4949+ .describe(
5050+ "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.",
5151+ );
5252+5353+export type AppBskyRichtextFacetLink = z.infer<
5454+ typeof appBskyRichtextFacetLinkSchema
5555+>;
5656+5757+export const appBskyRichtextFacetTagSchema = z
5858+ .object({
5959+ $type: z.literal("app.bsky.richtext.facet#tag"),
6060+ tag: z
6161+ .string()
6262+ .max(640)
6363+ .describe("The hashtag value (without leading '#')."),
6464+ })
6565+ .describe(
6666+ "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').",
6767+ );
6868+6969+export type AppBskyRichtextFacetTag = z.infer<
7070+ typeof appBskyRichtextFacetTagSchema
7171+>;
7272+7373+export const feature = z.discriminatedUnion("$type", [
7474+ appBskyRichtextFacetMentionSchema,
7575+ appBskyRichtextFacetLinkSchema,
7676+ appBskyRichtextFacetTagSchema,
7777+]);
7878+7979+export type Feature = z.infer<typeof feature>;
8080+8181+export const appBskyRichtextFacetSchema = z
8282+ .object({
8383+ index: appBskyRichtextFacetByteSliceSchema,
8484+ features: z.array(feature),
8585+ })
8686+ .describe("Annotation of a sub-string within rich text.");
8787+8888+export type AppBskyRichtextFacet = z.infer<typeof appBskyRichtextFacetSchema>;
···2233export const comAtprotoRepoStrongRefSchema = z
44 .object({
55- uri: z.string().describe("AT-URI of the referenced record."),
66- cid: z.string().describe("CID hash of the referenced record."),
55+ uri: z.string(),
66+ cid: z.string(),
77 })
88 .describe("A URI with a content-hash fingerprint.");
99