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 ("Lexicon SDL"), parsing to and printing from 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.

/// 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

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):

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

Library:

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.

ModuleWhat it does
token, lexerToken vocabulary; source -> spanned token stream
treeParser'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, cliEntry points incl. tree-wide parse_all; single-pass tree converter
errorSpanned errors + describe

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