import { parse, ValidationError, type BaseSchema } from "@atcute/lexicons/validations"; import { validateP2pdsRecord, loadP2pdsLexicons, getLexicon } from "./lexicons.js"; import { AppBskyActorProfile, AppBskyFeedGenerator, AppBskyFeedLike, AppBskyFeedPost, AppBskyFeedPostgate, AppBskyFeedRepost, AppBskyFeedThreadgate, AppBskyGraphBlock, AppBskyGraphFollow, AppBskyGraphList, AppBskyGraphListblock, AppBskyGraphListitem, AppBskyGraphStarterpack, AppBskyGraphVerification, AppBskyLabelerService, } from "@atcute/bluesky"; /** * Map of collection NSID to validation schema. */ const recordSchemas: Record = { "app.bsky.actor.profile": AppBskyActorProfile.mainSchema, "app.bsky.feed.generator": AppBskyFeedGenerator.mainSchema, "app.bsky.feed.like": AppBskyFeedLike.mainSchema, "app.bsky.feed.post": AppBskyFeedPost.mainSchema, "app.bsky.feed.postgate": AppBskyFeedPostgate.mainSchema, "app.bsky.feed.repost": AppBskyFeedRepost.mainSchema, "app.bsky.feed.threadgate": AppBskyFeedThreadgate.mainSchema, "app.bsky.graph.block": AppBskyGraphBlock.mainSchema, "app.bsky.graph.follow": AppBskyGraphFollow.mainSchema, "app.bsky.graph.list": AppBskyGraphList.mainSchema, "app.bsky.graph.listblock": AppBskyGraphListblock.mainSchema, "app.bsky.graph.listitem": AppBskyGraphListitem.mainSchema, "app.bsky.graph.starterpack": AppBskyGraphStarterpack.mainSchema, "app.bsky.graph.verification": AppBskyGraphVerification.mainSchema, "app.bsky.labeler.service": AppBskyLabelerService.mainSchema, }; // Load p2pds lexicons at module init so they're available for validation. loadP2pdsLexicons(); /** * Record validator for AT Protocol records. * Uses optimistic validation: known schemas are validated, unknown are allowed. * Delegates org.p2pds.* collections to the custom lexicon validator. */ export class RecordValidator { private strictMode: boolean; constructor(options: { strict?: boolean } = {}) { this.strictMode = options.strict ?? false; } validateRecord(collection: string, record: unknown): void { // Delegate p2pds collections to the custom lexicon validator if (collection.startsWith("org.p2pds.")) { validateP2pdsRecord(collection, record); return; } const schema = recordSchemas[collection]; if (!schema) { if (this.strictMode) { throw new Error( `No lexicon schema loaded for collection: ${collection}`, ); } return; } try { parse(schema, record); } catch (error) { if (error instanceof ValidationError) { throw new Error( `Lexicon validation failed for ${collection}: ${error.message}`, ); } throw error; } } hasSchema(collection: string): boolean { return collection in recordSchemas || getLexicon(collection) !== undefined; } getLoadedSchemas(): string[] { const p2pdsSchemas = Array.from(loadP2pdsLexicons().keys()); return [...Object.keys(recordSchemas), ...p2pdsSchemas]; } } export const validator = new RecordValidator({ strict: false });