tinylex#
tinylex is a tiny vanilla JavaScript library for validating atproto lexicons. Given a set of lexicon schemata — the JSON documents that describe atproto record types — tinylex extracts static types and validates record data against them. It conforms to Standard Schema, so you can use it anywhere you'd use schema validators like Zod/Valibot/etc.
Installation#
tinylex is meant to be vendored; simply copy and paste tinylex.js and tinylex-install into your project!
Usage#
A TinyLex instance holds your lexicon documents and resolves cross-references between them. You can handwrite or copy/paste lexicons inline or fetch them by NSID — either way, add them to the catalog, then call schema(nsid) to get a Standard Schema validator:
import TinyLex from "./tinylex.js";
const lexicons = new TinyLex().add({
lexicon: 1,
id: "com.example.follow",
defs: {
main: {
type: "record",
record: {
type: "object",
required: ["subject", "createdAt"],
properties: {
subject: { type: "string", format: "did" },
createdAt: { type: "string", format: "datetime" },
},
},
},
},
});
const schema = lexicons.schema("com.example.follow");
const result = schema["~standard"].validate({
subject: "did:plc:abc",
createdAt: "2025-01-01T00:00:00Z",
});
if (result.issues) console.error(result.issues);
else console.log(result.value.subject); // typed as string
schema(nsid) returns a Standard Schema v1 object, so the same validator plugs into any tool that speaks Standard Schema.
Fetching lexicons#
tinylex-install resolves lexicons by NSID over DNS (per the lexicon resolution spec) and writes them to disk:
./tinylex-install app.bsky.feed.post app.bsky.feed.like
./tinylex-install app.bsky.actor.profile --out ./schemata
./tinylex-install app.bsky.feed.post --js
Output defaults to ./lexicons/<nsid>.json. Pass --js to emit .js files wrapped in export default /** @type {const} */ ({...}) so imports keep literal types (otherwise TypeScript widens the JSON and TinyLex.add() can't infer narrow types for result.value).
The script is a polyglot shell/JS file that picks the first available runtime among Node, Deno and Bun.
Why?#
The official guide to installing lexicons uses the @atproto/lex package, which has 79 transitive dependencies and a requires a build step to actually use the lexicons in your code. @atcute/lex-cli is lighter weight, but requires a config file and a build step.
tinylex is ~650 lines of vanilla JavaScript with no dependencies. It lets you use the lexicon schema JSON as-is for validation and static typing. For convenience, it also includes a ~100 line install script that works with any JavaScript runtime to fetch lexicon documents based on their NSIDs.