a tiny atproto lexicon validator
2

Configure Feed

Select the types of activity you want to include in your feed.

JavaScript 88.7%
TypeScript 11.0%
Makefile 0.3%
6 1 0

Clone this repository

https://tangled.org/jakelazaroff.com/tinylex https://tangled.org/did:plc:vyntzvhqzh3u5ziqhj4xzama
git@tangled.org:jakelazaroff.com/tinylex git@tangled.org:did:plc:vyntzvhqzh3u5ziqhj4xzama

For self-hosted knots, clone URLs may differ based on your setup.



README.md

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.