Generates Elixir record modules from parsed lexicon IR.
For each app.bsky.* lexicon, generates one module for its main def
plus nested modules for internal #defs reachable from it:
app.bsky.feed.post(record) →Exosphere.Bsky.Feed.Postapp.bsky.richtext.facet#byteSlice→Exosphere.Bsky.Richtext.Facet.ByteSlice
Each generated module has:
- a struct with snake_cased fields plus an
extramap for unknown keys new/1— build and validate from atom- or string-keyed attrs, returning{:ok, struct} | {:error, [{path, message}]}to_map/1— encode to wire format (record mains inject"$type"; union encoders inject"$type"into their variants)from_map/1— decode a wire map (delegates tonew/1)type_id/0— the def's NSID ("app.bsky.feed.post","…#replyRef", or nil)
Cross-lexicon refs resolve when the target def is itself being generated;
unresolved refs degrade to pass-through term() values.
Summary
Functions
Generate all modules for lexicons (as from Parser.parse_dir/1).
Report refs the generator could not resolve: each is a
{source_nsid, ref, location} triple where ref points at an
unvendored lexicon (or a def the corpus does not contain), so the
affected fields degrade to pass-through term() values.
Write generated specs under lib_dir (default lib/exosphere),
creating directories as needed. Returns the paths written.
Types
@type spec() :: %{ module: module(), path: String.t(), nsid: String.t(), def_name: String.t() | nil, node: Exosphere.Lexicon.Parser.schema_node(), lexicon: Exosphere.Lexicon.Parser.lexicon() }
Functions
@spec generate( %{required(String.t()) => Exosphere.Lexicon.Parser.lexicon()}, keyword() ) :: [%{module: module(), path: String.t(), code: String.t()}]
Generate all modules for lexicons (as from Parser.parse_dir/1).
Options
:base— root module namespace (defaultExosphere). Withbase: MyApp,pub.oysters.postgenerates asMyApp.Pub.Oysters.Post.:rules— extra namespace rules:{prefix, [segments]}pairs checked before the built-ins, mapping an authority to a fixed module path ({"pub.oysters.", [Oysters, Lexicons]}→Oysters.Lexicons.Postinstead of the stutteringOysters.Pub.Oysters.Post).:external—%{{nsid, def_name} => module}map of defs that already exist as compiled modules elsewhere (e.g. the library's own generated corpus). Refs resolving into it point at the given module instead of generating a duplicate under:base— a host app referencingcom.atproto.repo.strongRefgetsExosphere.ATProto.Repo.StrongRef, not a second struct its library-typed code would reject.:seeds— NSIDs to generate from (default: all lexicons whosemaindef is a record or object). Refs still resolve against the fulllexiconsmap, so a host app can seed with its own lexicons while passing the vendored corpus alongside for ref resolution.
Namespace rules: app.bsky.* maps to <base>.Bsky.*,
com.atproto.* to <base>.ATProto.*, community.lexicon.* to
<base>.Community.*; any other authority maps by its full NSID
segments.
Generation starts from the seed lexicons whose main def is a record
or object (XRPC query/procedure/subscription and permission-set defs
are parsed but not yet generated), then walks refs transitively across
lexicons — including into main-less defs.json lexicons — generating a
nested module for each reachable object/record def. Refs to non-object
defs (e.g. string enum defs), to unvendored lexicons, and into
:external targets degrade to the referenced module or a pass-through
term() value.
Returns specs (module, path relative to the output dir, formatted code), ordered parents-first.
@spec unresolved_refs(%{required(String.t()) => Exosphere.Lexicon.Parser.lexicon()}) :: [ {source_nsid :: String.t(), ref :: String.t(), location :: String.t()} ]
Report refs the generator could not resolve: each is a
{source_nsid, ref, location} triple where ref points at an
unvendored lexicon (or a def the corpus does not contain), so the
affected fields degrade to pass-through term() values.
Write generated specs under lib_dir (default lib/exosphere),
creating directories as needed. Returns the paths written.