A fully-featured sdk for developing lexicons with typescript.
- atproto spec lexicon authoring with in IDE docs & hints for each attribute (ts => json)
- CLI to generate json from ts definitions
- CLI to generate ts from json definitions
- inference of usage type from full lexicon definition
- the really cool part of this is that it fills in the refs from the defs all at the type level
lx.lexicon(...).validate(data)for validating data using@atproto/lexiconfromJSON()helper for creating lexicons directly from JSON objects with full type inference
npm install prototypeyPrototypey provides both a TypeScript library for authoring lexicons and a CLI for code generation.
what you'll write:
const lex = lx.lexicon("app.bsky.actor.profile", {
main: lx.record({
key: "self",
record: lx.object({
displayName: lx.string({ maxLength: 64, maxGraphemes: 64 }),
description: lx.string({ maxLength: 256, maxGraphemes: 256 }),
}),
}),
});generates to:
{
"lexicon": 1,
"id": "app.bsky.actor.profile",
"defs": {
"main": {
"type": "record",
"key": "self",
"record": {
"type": "object",
"properties": {
"displayName": {
"type": "string",
"maxLength": 64,
"maxGraphemes": 64
},
"description": {
"type": "string",
"maxLength": 256,
"maxGraphemes": 256
}
}
}
}
}
}you could also access the json definition with lex.json().
Prototypey provides runtime validation using @atproto/lexicon:
const lex = lx.lexicon("app.bsky.actor.profile", {
main: lx.record({
key: "self",
record: lx.object({
displayName: lx.string({ maxLength: 64, maxGraphemes: 64 }),
description: lx.string({ maxLength: 256, maxGraphemes: 256 }),
}),
}),
});
// Validate data against the schema
const result = lex.validate({
displayName: "Alice",
description: "Software engineer",
});
if (result.success) {
console.log("Valid data:", result.value);
} else {
console.error("Validation error:", result.error);
}Validating against specific definitions:
If your lexicon has multiple definitions, you can validate against a specific one:
const lex = lx.lexicon("app.bsky.feed.post", {
user: lx.object({
handle: lx.string({ required: true }),
displayName: lx.string(),
}),
main: lx.record({
key: "tid",
record: lx.object({
text: lx.string({ required: true }),
author: lx.ref("#user", { required: true }),
}),
}),
});
// Validate against the "user" definition
const userResult = lex.validate(
{ handle: "alice.bsky.social", displayName: "Alice" },
"user",
);
// Validate against "main" (default if not specified)
const postResult = lex.validate({
text: "Hello world",
author: { handle: "bob.bsky.social" },
});The prototypey package includes a CLI with two main commands:
prototypey gen-emit <outdir> <sources...>Extracts JSON schemas from TypeScript lexicon definitions.
Example:
prototypey gen-emit ./lexicons ./src/lexicons/**/*.tsprototypey gen-from-json <outdir> <sources...>Generates TypeScript files from JSON lexicon schemas using the fromJSON helper. This is useful when you have existing lexicon JSON files and want to work with them in TypeScript with full type inference.
Example:
prototypey gen-from-json ./src/lexicons ./lexicons/**/*.jsonThis will create TypeScript files that export typed lexicon objects:
// Generated file: src/lexicons/app.bsky.feed.post.ts
import { fromJSON } from "prototypey";
export const appBskyFeedPost = fromJSON({
// ... lexicon JSON
});- Author lexicons in TypeScript using the library
- Emit JSON schemas with
gen-emitfor runtime validation
Recommended: Add as a script to your package.json:
{
"scripts": {
"lexicon:emit": "prototypey gen-emit ./schemas ./src/lexicons/**/*.ts"
}
}Then run:
npm run lexicon:emit- Start with JSON lexicon schemas (e.g., from atproto)
- Generate TypeScript with
gen-from-jsonfor type-safe access
Recommended: Add as a script to your package.json:
{
"scripts": {
"lexicon:import": "prototypey gen-from-json ./src/lexicons ./lexicons/**/*.json"
}
}Then run:
npm run lexicon:importPlease give any and all feedback. I've not really written many lexicons much myself yet, so this project is at a point of "well I think this makes sense". Both the issues page and discussions are open and ready for y'all 🙂.
Call For Contribution:
- We need library art! Please reach out if you'd be willing to contribute some drawings or anything :)