# atproto_sdl

> **Alpha, pre-release.** Grammar, CLI flags, and printed output are all subject to
> change; expect breaking changes between 0.x releases.

A Prisma/GraphQL-inspired schema language for
[atproto lexicons](https://atproto.com/specs/lexicon) ("Lexicon SDL"),
parsing to and printing from [`atproto_lexicon`](https://hex.pm/packages/atproto_lexicon)'s AST.
The printer is total: every AST construct has a spelling, so lexicon JSON
round-trips through SDL with zero exception files. Design authority:
[`docs/lexicon-sdl.md`](https://tangled.org/@niels.atproto.fan/gleam-atproto/blob/main/docs/lexicon-sdl.md).

```
/// A forum-style shelf entry.
record shelfEntry @key(tid) {
  title: String! @len(1, 200)
  tags: [String @len(max: 64)] @max(10)
  createdAt: Datetime!
}

query listReleases(q: String, limit: Int = 25 @range(1, 100)): releasePage
  throws NotFound | Expired
```

Authoring sugar, expanding at compile time to plain inline lexicon JSON
(never a `$ref`, never a new def):

```
scalar Text64 = String @len(max: 640) @graphemes(max: 64)
values Grade = "M" | "NM" | "VG+"

record entry {
  ...dev.example.crate.defs#releaseDisplay
  title: Text64!
  mediaGrade: String @known(Grade)
}
```

Cross-file spreads and sugar references resolve during tree-wide
compilation (`atproto_sdl.parse_all`, or the CLI, which registers every file
in the tree).

## Installation

```sh
gleam add atproto_sdl
```

## Usage

CLI, converting a tree of `.sdl`/`.json` files to the other format (NSID
derives from the file path; `--to json|sdl` restricts direction):

```sh
gleam run -m atproto_sdl -- <src-dir> <out-dir> [--to json|sdl]
```

Library:

```gleam
import atproto_sdl
import atproto_lexicon/encoding

let assert Ok(doc) = atproto_sdl.parse(source, "com.example.thing")
let sdl_text = atproto_sdl.print(doc)
let json_text = encoding.to_json_string(doc)
```

## Architecture

Text -> `lexer` -> `parser` (intermediate `tree`) -> `lower` -> `ast.LexiconDoc` -> `printer` -> text.

| Module                  | What it does                                                                                             |
| ----------------------- | -------------------------------------------------------------------------------------------------------- |
| `token`, `lexer`        | Token vocabulary; source -> spanned token stream                                                         |
| `tree`                  | Parser's intermediate tree                                                                               |
| `parser` + `parser/*`   | Tokens -> tree (`cursor`, `values`, `types` submodules)                                                  |
| `lower` + `lower/*`     | Tree -> AST (`scalars`, `items`, `sugar`, `prepared`, `registry`, `scope`, `attrs`, `fields`, `methods`) |
| `printer` + `printer/*` | AST -> SDL (`render`, `constraints`, `types`, `methods`); depends only on `atproto_lexicon/ast`          |
| `pipeline`, `cli`       | Entry points incl. tree-wide `parse_all`; single-pass tree converter                                     |
| `error`                 | Spanned errors + `describe`                                                                              |

Corpus status: 28/28 at-record lexicons and 25/25 vendored corpus files
round-trip decode -> print -> parse, print-idempotent (`gleam test`).
