# atproto_lexicon
> **Alpha.** Pre-1.0, evolving alongside
> [at-record](https://tangled.org/@mokkenstorm.dev/at-record); expect breaking
> changes between 0.x releases.
A faithful, bidirectional [atproto lexicon](https://atproto.com/specs/lexicon)
AST for Gleam. `encode(decode(json))` is structurally equal to the source for
every corpus file (key order aside); absent optional fields stay absent.
Unrecognized keys are dropped on decode (documented fidelity gap); an
unrecognized `type` discriminator is a decode error.
## Installation
Monorepo path dependency:
```toml
[dependencies]
atproto_lexicon = { path = "../atproto_lexicon" }
```
## Usage
```gleam
import atproto_lexicon/decoding
import atproto_lexicon/encoding
let assert Ok(doc) = decoding.decode_json(lexicon_json)
let round_tripped = encoding.to_json_string(doc)
```
## Architecture
| Module | What it does |
| ---------- | ---------------------------------------------------------------------------------- |
| `ast` | The lexicon types; def-position vs. property-position type split |
| `decoding` | `decode_json`/`decode_dynamic`, composable `document_decoder()`, structured errors |
| `encoding` | `encode`/`to_json_string` |
| `diff` | Semantic doc diff, breaking vs. non-breaking classification (CI drift gate) |
| `source` | Spec parsing + network resolution: NSID authority -> DNS -> DID -> PDS -> record |
| `pin` | Pinned schema cache with `lexicons.lock.json`; `refresh` online, `read` offline |
This is the shared core of the lexicon toolchain
([`docs/lexicon-toolchain.md`](../docs/lexicon-toolchain.md)): the syntax
front-ends [`atproto_mlf`](../atproto_mlf) and [`atproto_sdl`](../atproto_sdl)
and the codegen back-end [`atproto_codegen`](../atproto_codegen) all meet at
`atproto_lexicon/ast`.
## Development
`gleam test`. The corpus test walks `test/fixtures/**/*.json` (vendored
at-record snapshot); refresh by re-copying and bumping the count in
`test/corpus_test.gleam`.