typelex

An experimental TypeSpec syntax for Lexicon

Typelex

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

Typelex

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

Typelex

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

Typelex

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

Try the playground

Open Playground

Experiment with typelex in your browser before installing.

1

Add typelex to your app

npx @typelex/cli init

This 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:typelex

Your 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.