# tinylex tinylex is a tiny vanilla JavaScript library for validating [atproto lexicons](https://atproto.com/guides/lexicon). 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](https://standardschema.dev/schema), so you can use it anywhere you'd use schema validators like Zod/Valibot/etc. ## Installation tinylex is meant to be [vendored](https://htmx.org/essays/vendoring/); 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](#fetching-lexicons) — either way, add them to the catalog, then call `schema(nsid)` to get a Standard Schema validator: ```js 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](https://standardschema.dev/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](https://atproto.com/specs/lexicon#lexicon-resolution)) and writes them to disk: ```sh ./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/.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](https://atproto.com/guides/installing-lexicons) uses the [`@atproto/lex`](https://npmx.dev/package/@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`](https://npmx.dev/package/@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.