# @cerulea/lexicon define AT Protocol lexicon schemas in TypeScript - no deps :D - runtime validation via `schema.validate(value)` - returns `{ success: true, value }` or `{ success: false, issues }` with a path per issue - emit lexicon JSON documents via `toLexicons(...roots)` - easily generate a `lexicons/` dir of json from typescript defs; it's like a three-liner - simple record builder via `schema.build(...)` - will auto-fill `$type` for records - will expand short `#fragment` strings for unions ## usage ```typescript import * as l from "@cerulea/lexicon"; const facetRange = { start: l.integer, end: l.integer }; const Facet = l.union("example.myapp.facet", { "#mention": l.object({ ...facetRange, did: l.did }), "#link": l.object({ ...facetRange, uri: l.uri }), }); const Post = l.record("example.myapp.post", { key: "tid" }, { text: l.stringWith({ maxGraphemes: 300 }), facets: l.optional(l.array(Facet)), createdAt: l.datetime, }); type Post = l.Infer; const getPost = l.query("example.myapp.getPost", { parameters: l.params({ uri: l.atUri }), output: Post, errors: [{ name: "NotFound" }], }); const post: Post = Post.build({ text: "hi @alice", facets: [{ $type: "#mention", start: 3, end: 9, did: "did:example:alice" }], createdAt: new Date().toISOString(), }); const untrusted: unknown = { $type: "example.myapp.post", text: "hello", createdAt: "my evil invalid date", }; const result = Post.validate(untrusted); if (!result.success) console.error(result.issues); const lexicons = l.toLexicons(Post, getPost); /* [ { lexicon: 1, id: "example.myapp.facet", … }, { lexicon: 1, id: "example.myapp.getPost", … }, … ] */ ```