typelex
An experimental TypeSpec syntax for Lexicon
import "@typelex/emitter";
namespace app.bsky.actor.profile {
@rec("self")
model Main {
@maxLength(64)
@maxGraphemes(64)
displayName?: string;
@maxLength(256)
@maxGraphemes(256)
description?: string;
}
}Lexicon
{
"lexicon": 1,
"id": "app.bsky.actor.profile",
"defs": {
"main": {
"type": "record",
"key": "self",
"record": {
"type": "object",
"properties": {
"displayName": {
"type": "string",
"maxLength": 64,
"maxGraphemes": 64
},
"description": {
"type": "string",
"maxLength": 256,
"maxGraphemes": 256
}
}
}
}
}
}
Typelex lets you write AT Lexicons in a more readable syntax.
It uses TypeSpec for syntax, adding conventions for Lexicons.
Records and properties
import "@typelex/emitter";
namespace fm.teal.alpha.feed.play {
@rec("tid")
model Main {
@maxItems(10)
artistNames?: string[];
@required
@minLength(1)
@maxLength(256)
trackName: string;
@required
playedTime: datetime;
}
}Lexicon
{
"lexicon": 1,
"id": "fm.teal.alpha.feed.play",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"properties": {
"artistNames": {
"type": "array",
"items": {"type": "string"},
"maxLength": 10
},
"trackName": {"type": "string", "maxLength": 256, "minLength": 1},
"playedTime": {"type": "string", "format": "datetime"}
},
"required": ["trackName", "playedTime"]
}
}
}
}Refs and unions
import "@typelex/emitter";
namespace app.bsky.feed.post {
@rec("tid")
model Main {
@required
@maxLength(3000)
@maxGraphemes(300)
text: string;
@maxItems(3)
langs?: language[];
embed?: Images | Video | unknown;
}
model Images {
@required
@maxItems(4)
images: Image[];
}
model Image {
@required image: Blob<#["image/*"], 1000000>;
@required alt: string;
}
model Video {
@required video: Blob<#["video/mp4"], 100000000>;
alt?: string;
}
}Lexicon
{
"lexicon": 1,
"id": "app.bsky.feed.post",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"properties": {
"text": {"type": "string", "maxLength": 3000, "maxGraphemes": 300},
"langs": {
"type": "array",
"items": {"type": "string", "format": "language"},
"maxLength": 3
},
"embed": {"type": "union", "refs": ["#images", "#video"]}
},
"required": ["text"]
}
},
"images": {
"type": "object",
"properties": {
"images": {
"type": "array",
"items": {"type": "ref", "ref": "#image"},
"maxLength": 4
}
},
"required": ["images"]
},
"image": {
"type": "object",
"properties": {
"image": {"type": "blob", "accept": ["image/*"], "maxSize": 1000000},
"alt": {"type": "string"}
},
"required": ["image", "alt"]
},
"video": {
"type": "object",
"properties": {
"video": {
"type": "blob",
"accept": ["video/mp4"],
"maxSize": 100000000
},
"alt": {"type": "string"}
},
"required": ["video"]
}
}
}Queries and params
import "@typelex/emitter";
namespace com.atproto.repo.listRecords {
@query
op main(
@required repo: did,
@required collection: string,
@minValue(1)
@maxValue(100)
limit?: integer = 50,
cursor?: string,
reverse?: boolean
): ListOutput;
@inline
model ListOutput {
cursor?: string;
@required
records: Record[];
}
model Record {
@required uri: atUri;
@required cid: cid;
@required value: unknown;
}
}Lexicon
{
"lexicon": 1,
"id": "com.atproto.repo.listRecords",
"defs": {
"main": {
"type": "query",
"parameters": {
"type": "params",
"properties": {
"repo": {"type": "string", "format": "did"},
"collection": {"type": "string"},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": {"type": "string"},
"reverse": {"type": "boolean"}
},
"required": ["repo", "collection"]
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {
"cursor": {"type": "string"},
"records": {
"type": "array",
"items": {"type": "ref", "ref": "#record"}
}
},
"required": ["records"]
}
}
},
"record": {
"type": "object",
"properties": {
"uri": {"type": "string", "format": "at-uri"},
"cid": {"type": "string", "format": "cid"},
"value": {"type": "unknown"}
},
"required": ["uri", "cid", "value"]
}
}
}Install
This is an early-stage experiment. Design and syntax may change.
0
1
Add typelex to your app
npx @typelex/cli initThis will add a few things to your package.json and create a typelex/ folder.
2
Write your lexicons in typelex/main.tsp
import "@typelex/emitter";
import "./externals.tsp";
namespace com.myapp.example.profile {
/** My profile. */
@rec("literal:self")
model Main {
/** Free-form profile description.*/
@maxGraphemes(256)
description?: string;
}
}Your app's lexicons go here. They may reference any external ones from lexicons/.
3
Compile your lexicons
npm run build:typelexYour app’s compiled lexicons will appear in lexicons/ alongside any external ones.
4
Set up VS Code
Install the TypeSpec for VS Code extension for syntax highlighting and IntelliSense.
5
Learn more
Check out the documentation to learn more.