An experimental TypeSpec syntax for Lexicon

Compare changes

Choose any two refs to compare.

Changed files
+3517 -851
packages
cli
emitter
lib
src
test
integration
atproto
input
app
bsky
actor
bookmark
embed
feed
graph
labeler
notification
unspecced
video
chat
com
tools
lexicon-examples
spec
basic
input
com
output
external
input
output
example
playground
website
src
pages
+12
CHANGELOG.md
··· 1 ### 0.1.4 2 3 - Fix some namespaces failing to emit
··· 1 + ### 0.2.0 2 + 3 + - Add `@external` support 4 + 5 + ### 0.1.6 6 + 7 + - Rebuild 8 + 9 + ### 0.1.5 10 + 11 + - Allow tokens in string unions 12 + 13 ### 0.1.4 14 15 - Fix some namespaces failing to emit
+11 -34
CLAUDE.md
··· 1 - you're working on a TypeSpec emitter for atproto lexicons. you have these commands: `pnpm run build` builds the example (you can inspect the generated lexicons in the `packages/example/lexicon`) and `pnpm test` runs tests (they are in `packages/emitter/test/`). 2 3 - what i want you to do is to gradually keep adding features to typelex so that in the end it's possible to express all Lexicon code with that language. you have the following resources: 4 5 - - read `DOCS.md`!!! it's the main guideline 6 7 - - `../typespec/packages` has a bunch of other emitters (so you can check how they're implemented and common patterns) 8 9 - - `../typespec/website/src/content` has typescpec docs (which you might find very useful) 10 11 - - `../atproto-website/src/app/[locale]/specs/lexicon` contains lexicon spec (where we want to reach feature parity), and most importantly 12 13 - - `test/scenarios/atproto/output` contains a TON of lexicon definitions which i want you to translate to typespec one by one. for each atproto lexicon you're porting, your job is to create the corresponding "`input`s" for them (and fix bugs or add missing features along the way). 14 15 - the goal is to end up with a language that feels nice and concise, not some weird interop thing. so try to do things "typespec" way as you go through each lexicon. and try to respect atproto too. this should feel like a *language for atproto*. consider whether your design is elegant. and make sure to look at other typespec emitters for design ideas! 16 - 17 - remember NOT to change the output to fit the input -- your job is to make the input MATCH the output. if some lexicon is too hard, you can give up on it and try another one. good luck! 18 - 19 - the workflow is: pick some new lexicons, create the input files, figure out missing features / bugs, and try to get tests to pass. repeat. when you make nontrivial progress, you'll want to prepare a commit. 20 - 21 - before you want to make a commit, make sure `pnpm test` and `pnpm run build` passes and, ideally, inspect the `example` app output (and maybe add the features you've just implemented there for parity). then stop and notify me. 22 - 23 - ps. be mindful of the code style. we only put decorators on same line if it's `@required` alone; otherwise, put each on newline and add a newline after. see existing files for a hint. 24 25 good luck! i believe in you 26 27 - 28 - --- 29 - 30 - whenever you get stuck (try `npm test`) because you don't have a convention for how to define some pattern yetm i want you to read these sources 31 - 32 - - `../typespec/packages` has a bunch of other emitters (so you can check how they're implemented and common patterns) 33 - 34 - - `../typespec/website/src/content` has typescpec docs (which you might find very useful) 35 - 36 - - `../atproto-website/src/app/[locale]/specs/lexicon` contains lexicon spec 37 - 38 - and ultrathink about what's the most appropriate way to define defs like you want would be in this typespec translation of atproto. you can research these sources for what feels most idiomatic to define that syntax, and how to make 39 - the formatter understand that convention and emit the JSON we want. 40 - 41 - also note that you can always look at generated atproto lexicons in ../atproto/lexicons -- use these!! look at the TS types there for corresponding 42 - models to inspire how you represent things in tsp syntax. 43 - 44 - - read `DOCS.md`!!! it's the main guideline
··· 1 + you're working on a TypeSpec emitter for atproto Lexicons. the goal is to make a more comfortable language for writing lexicons, kind of like "coffeescript for lexicons" (however terrible that may be). one may imagine it getting supplanted by a proper atproto IDL someday. however, we're staying within what TypeSpec offers, which means we want to stay idiomatic both to atproto and TypeSpec, or rather, to try to translate atproto idioms into TypeSpec idioms as closely as we can. 2 3 + you have the following resources you're encouraged to explore on your own whenever they seem relevant to the task: 4 5 + - `DOCS.md` is a must-read. it gives you an intro to typelex and how its conventions map to atproto. it also gives you a sense of the project philosophy. 6 + - `../atproto-website/` has a Lexicon spec (look for `lexicon/en.mdx`) which you'll want to consult 7 + - `../typespec/` is the TypeSpec repo. you can consult the source code of other emitters (in `packages`, e.g. `packages/protobuf` and `packages/json-schema`) and `website` folder for documentation of TypeSpec syntax and idioms. 8 9 + as a part of your workflow, you will: 10 11 + - run `pnpm test` whenever you make changes to verify no regressions. note a few test suites: an `atproto` test suite with definitions from upstream (`.tsp` files produce expected `.json` files), `lexicon-examples` suite with third party lexicons, and `spec` with a more focused suite. use checked-in `*.tsp` files liberally to learn typelex conventions. we have a LOT of them! 12 13 + - if you're working on `packages/example`, use `pnpm run build` to run it and observe the output. you may also want to look at codegen output (it's hooked up to atproto codegen). 14 15 + - we also have a playground in `packages/playground` and website in `packages/website`. 16 17 + you'll approach the project thoughtfully. while `playground` and `website` are more vibey and can be garbage code, it's essential to keep the `emitter` making sense. this doesn't just mean always verifying the tests pass (that's a given), but also making each decision *based on the spec* and a good understanding of atproto semantics. always think: is this the simplest solution? does it still make sense if you forget the code that exists now and think from first principles? don't hesitate to pause and ask or rethink if something actually doesn't make sense. 18 19 good luck! i believe in you 20 21 + and read the `DOCS.md` (and `*.tsp` files in this repo)!!! they're your most important sources of information.
+24 -3
DOCS.md
··· 238 239 ### External Stubs 240 241 - If you don't have TypeSpec definitions for external Lexicons, you can stub them out: 242 243 ```typescript 244 import "@typelex/emitter"; ··· 250 } 251 252 // Empty stub (like .d.ts in TypeScript) 253 namespace com.atproto.label.defs { 254 model SelfLabels { } 255 } 256 ``` 257 258 - You could collect stubs in one file and import them: 259 260 ```typescript 261 import "@typelex/emitter"; ··· 268 } 269 ``` 270 271 - You'll want to replace the stubbed lexicons in the output folder with their real JSON before running codegen. 272 273 ### Inline Models 274
··· 238 239 ### External Stubs 240 241 + If you don't have TypeSpec definitions for external Lexicons, you can stub them out using the `@external` decorator: 242 243 ```typescript 244 import "@typelex/emitter"; ··· 250 } 251 252 // Empty stub (like .d.ts in TypeScript) 253 + @external 254 namespace com.atproto.label.defs { 255 model SelfLabels { } 256 } 257 ``` 258 259 + The `@external` decorator tells the emitter to skip JSON output for that namespace. This is useful when referencing definitions from other Lexicons that you don't want to re-emit. 260 + 261 + You could collect external stubs in one file and import them: 262 263 ```typescript 264 import "@typelex/emitter"; ··· 271 } 272 ``` 273 274 + Then in `atproto-stubs.tsp`: 275 + 276 + ```typescript 277 + import "@typelex/emitter"; 278 + 279 + @external 280 + namespace com.atproto.label.defs { 281 + model SelfLabels { } 282 + } 283 + 284 + @external 285 + namespace com.atproto.repo.defs { 286 + model StrongRef { } 287 + @token model SomeToken { } // Note: Tokens still need @token 288 + } 289 + // ... more stubs 290 + ``` 291 + 292 + You'll want to ensure the real JSON for external Lexicons is available before running codegen. 293 294 ### Inline Models 295
+5 -3
README.md
··· 4 5 See https://typelex.org/ 6 7 - **This is an early-stage experiment. Itโ€™s probably buggy as hell.** 8 - 9 Design is not final and might change. Ideas welcome. 10 11 ## Playground ··· 14 15 ## Documentation 16 17 - No "proper" docs yet. See [DOCS.md](./DOCS.md) for now. 18 19 ## License 20
··· 4 5 See https://typelex.org/ 6 7 Design is not final and might change. Ideas welcome. 8 9 ## Playground ··· 12 13 ## Documentation 14 15 + See [DOCS.md](./DOCS.md). 16 + 17 + ## Alternatives 18 + 19 + * [MFL](https://mlf.lol/) 20 21 ## License 22
+2 -1
package.json
··· 10 "example": "pnpm --filter @typelex/example build", 11 "playground": "pnpm --filter @typelex/playground dev", 12 "validate": "pnpm build && pnpm run validate-lexicons && pnpm test", 13 - "validate-lexicons": "node scripts/validate-lexicons.js" 14 }, 15 "repository": { 16 "type": "git",
··· 10 "example": "pnpm --filter @typelex/example build", 11 "playground": "pnpm --filter @typelex/playground dev", 12 "validate": "pnpm build && pnpm run validate-lexicons && pnpm test", 13 + "validate-lexicons": "node scripts/validate-lexicons.js", 14 + "cli": "pnpm --filter @typelex/cli" 15 }, 16 "repository": { 17 "type": "git",
+3
packages/cli/.gitignore
···
··· 1 + dist 2 + node_modules 3 + *.log
+66
packages/cli/README.md
···
··· 1 + # @typelex/cli 2 + 3 + Experimental CLI for typelex 4 + 5 + ## Installation 6 + 7 + ```bash 8 + pnpm add -D @typelex/cli @typelex/emitter 9 + ``` 10 + 11 + ## Usage 12 + 13 + ```bash 14 + typelex compile xyz.statusphere.* 15 + ``` 16 + 17 + This command: 18 + 1. Scans `lexicons/` for all external lexicons (not matching `xyz.statusphere`) 19 + 2. Generates `typelex/externals.tsp` with `@external` stubs 20 + 3. Compiles `typelex/main.tsp` to `lexicons/` (or custom output via `--out`) 21 + 22 + Fixed paths: 23 + - Entry point: `typelex/main.tsp` 24 + - Externals: `typelex/externals.tsp` 25 + 26 + ## Example 27 + 28 + ```typescript 29 + // typelex/main.tsp 30 + import "@typelex/emitter"; 31 + import "./externals.tsp"; 32 + 33 + namespace xyz.statusphere.defs { 34 + model StatusView { 35 + @required uri: atUri; 36 + @required status: string; 37 + @required profile: app.bsky.actor.defs.ProfileView; 38 + } 39 + } 40 + ``` 41 + 42 + ```bash 43 + typelex compile 'xyz.statusphere.*' 44 + ``` 45 + 46 + The CLI scans `lexicons/` for external types and auto-generates `typelex/externals.tsp` with stubs 47 + 48 + ### Integration 49 + 50 + ```json 51 + { 52 + "scripts": { 53 + "build:lexicons": "typelex compile 'xyz.statusphere.*'", 54 + "build:codegen": "lex gen-server --yes ./src lexicons/xyz/statusphere/*.json" 55 + } 56 + } 57 + ``` 58 + 59 + ## Options 60 + 61 + - `--out <directory>` - Output directory for generated Lexicon files (default: `./lexicons`) 62 + - `--watch` - Watch mode for continuous compilation 63 + 64 + ## License 65 + 66 + MIT
+40
packages/cli/package.json
···
··· 1 + { 2 + "name": "@typelex/cli", 3 + "version": "0.2.0", 4 + "description": "CLI for typelex - TypeSpec-based IDL for ATProto Lexicons", 5 + "main": "dist/index.js", 6 + "type": "module", 7 + "bin": { 8 + "typelex": "dist/cli.js" 9 + }, 10 + "files": [ 11 + "dist", 12 + "src" 13 + ], 14 + "scripts": { 15 + "build": "tsc", 16 + "clean": "rm -rf dist", 17 + "watch": "tsc --watch", 18 + "prepublishOnly": "npm run build" 19 + }, 20 + "keywords": [ 21 + "typespec", 22 + "atproto", 23 + "lexicon", 24 + "cli" 25 + ], 26 + "author": "Dan Abramov <dan.abramov@gmail.com>", 27 + "license": "MIT", 28 + "dependencies": { 29 + "@typespec/compiler": "^1.4.0", 30 + "yargs": "^18.0.0" 31 + }, 32 + "devDependencies": { 33 + "@types/node": "^20.0.0", 34 + "@types/yargs": "^17.0.33", 35 + "typescript": "^5.0.0" 36 + }, 37 + "peerDependencies": { 38 + "@typelex/emitter": "^0.2.0" 39 + } 40 + }
+64
packages/cli/src/cli.ts
···
··· 1 + #!/usr/bin/env node 2 + import yargs from "yargs"; 3 + import { hideBin } from "yargs/helpers"; 4 + import { compileCommand } from "./commands/compile.js"; 5 + 6 + async function main() { 7 + await yargs(hideBin(process.argv)) 8 + .scriptName("typelex") 9 + .usage("$0 compile <namespace>") 10 + .command( 11 + "compile <namespace>", 12 + "Compile TypeSpec files to Lexicon JSON", 13 + (yargs) => { 14 + return yargs 15 + .positional("namespace", { 16 + describe: "Primary namespace pattern (e.g., app.bsky.*)", 17 + type: "string", 18 + demandOption: true, 19 + }) 20 + .option("out", { 21 + describe: "Output directory for generated Lexicon files (relative to cwd)", 22 + type: "string", 23 + default: "./lexicons", 24 + }); 25 + }, 26 + async (argv) => { 27 + const options: Record<string, unknown> = {}; 28 + if (argv.watch) { 29 + options.watch = true; 30 + } 31 + if (argv.out) { 32 + options.out = argv.out; 33 + } 34 + await compileCommand(argv.namespace, options); 35 + } 36 + ) 37 + .option("watch", { 38 + describe: "Watch mode", 39 + type: "boolean", 40 + default: false, 41 + }) 42 + .demandCommand(1, "You must specify a command") 43 + .help() 44 + .version() 45 + .fail((msg, err) => { 46 + if (err) { 47 + console.error(err); 48 + } else { 49 + console.error(msg); 50 + } 51 + process.exit(1); 52 + }).argv; 53 + } 54 + 55 + process.on("unhandledRejection", (error: unknown) => { 56 + console.error("Unhandled promise rejection!"); 57 + console.error(error); 58 + process.exit(1); 59 + }); 60 + 61 + main().catch((error) => { 62 + console.error(error); 63 + process.exit(1); 64 + });
+68
packages/cli/src/commands/compile.ts
···
··· 1 + import { resolve } from "path"; 2 + import { spawn } from "child_process"; 3 + import { generateExternalsFile } from "../utils/externals-generator.js"; 4 + import { ensureMainImports } from "../utils/ensure-imports.js"; 5 + 6 + /** 7 + * Compile TypeSpec files to Lexicon JSON 8 + * 9 + * @param namespace - Primary namespace pattern (e.g., "app.bsky.*") 10 + * @param options - Additional compiler options 11 + */ 12 + export async function compileCommand( 13 + namespace: string, 14 + options: Record<string, unknown> = {} 15 + ): Promise<void> { 16 + const cwd = process.cwd(); 17 + const outDir = (options.out as string) || "./lexicons"; 18 + 19 + // Validate that output directory ends with 'lexicons' 20 + const normalizedPath = outDir.replace(/\\/g, '/').replace(/\/+$/, ''); 21 + if (!normalizedPath.endsWith('/lexicons') && normalizedPath !== 'lexicons' && normalizedPath !== './lexicons') { 22 + console.error(`Error: Output directory must end with 'lexicons'`); 23 + console.error(`Got: ${outDir}`); 24 + console.error(`Valid examples: ./lexicons, ../../lexicons, /path/to/lexicons`); 25 + process.exit(1); 26 + } 27 + 28 + // Generate externals first (scans the output directory for external lexicons) 29 + await generateExternalsFile(namespace, cwd, outDir); 30 + 31 + // Ensure required imports are present in main.tsp 32 + await ensureMainImports(cwd); 33 + 34 + // Compile TypeSpec using the TypeSpec CLI 35 + const entrypoint = resolve(cwd, "typelex/main.tsp"); 36 + const args = [ 37 + "compile", 38 + entrypoint, 39 + "--emit", 40 + "@typelex/emitter", 41 + "--option", 42 + `@typelex/emitter.emitter-output-dir={project-root}/${outDir}`, 43 + ]; 44 + 45 + if (options.watch) { 46 + args.push("--watch"); 47 + } 48 + 49 + return new Promise((resolve, reject) => { 50 + const tsp = spawn("tsp", args, { 51 + cwd, 52 + stdio: "inherit", 53 + }); 54 + 55 + tsp.on("close", (code) => { 56 + if (code === 0) { 57 + resolve(); 58 + } else { 59 + process.exit(code ?? 1); 60 + } 61 + }); 62 + 63 + tsp.on("error", (err) => { 64 + console.error("Failed to start TypeSpec compiler:", err); 65 + reject(err); 66 + }); 67 + }); 68 + }
+1
packages/cli/src/index.ts
···
··· 1 + export { compileCommand } from "./commands/compile.js";
+38
packages/cli/src/utils/ensure-imports.ts
···
··· 1 + import { readFile } from "fs/promises"; 2 + import { resolve } from "path"; 3 + 4 + const REQUIRED_FIRST_LINE = 'import "@typelex/emitter";'; 5 + const REQUIRED_SECOND_LINE = 'import "./externals.tsp";'; 6 + 7 + /** 8 + * Validates that main.tsp starts with the required imports. 9 + * Fails the build if the first two lines are not exactly as expected. 10 + * 11 + * @param cwd - Current working directory 12 + */ 13 + export async function ensureMainImports(cwd: string): Promise<void> { 14 + const mainPath = resolve(cwd, "typelex/main.tsp"); 15 + 16 + try { 17 + const content = await readFile(mainPath, "utf-8"); 18 + const lines = content.split("\n"); 19 + 20 + if (lines[0]?.trim() !== REQUIRED_FIRST_LINE) { 21 + console.error(`Error: main.tsp must start with: ${REQUIRED_FIRST_LINE}`); 22 + console.error(`Found: ${lines[0] || "(empty line)"}`); 23 + process.exit(1); 24 + } 25 + 26 + if (lines[1]?.trim() !== REQUIRED_SECOND_LINE) { 27 + console.error(`Error: Line 2 of main.tsp must be: ${REQUIRED_SECOND_LINE}`); 28 + console.error(`Found: ${lines[1] || "(empty line)"}`); 29 + process.exit(1); 30 + } 31 + } catch (err) { 32 + if ((err as NodeJS.ErrnoException).code === "ENOENT") { 33 + console.error("Error: typelex/main.tsp not found"); 34 + process.exit(1); 35 + } 36 + throw err; 37 + } 38 + }
+105
packages/cli/src/utils/externals-generator.ts
···
··· 1 + import { resolve } from "path"; 2 + import { writeFile, mkdir } from "fs/promises"; 3 + import { findExternalLexicons, LexiconDoc, isTokenDef, isModelDef } from "./lexicon.js"; 4 + 5 + /** 6 + * Convert camelCase to PascalCase 7 + */ 8 + function toPascalCase(str: string): string { 9 + return str.charAt(0).toUpperCase() + str.slice(1); 10 + } 11 + 12 + /** 13 + * Extract namespace prefix from pattern (e.g., "app.bsky.*" -> "app.bsky") 14 + */ 15 + function getNamespacePrefix(pattern: string): string { 16 + if (!pattern.endsWith(".*")) { 17 + throw new Error(`Namespace pattern must end with .*: ${pattern}`); 18 + } 19 + return pattern.slice(0, -2); 20 + } 21 + 22 + /** 23 + * Generate TypeSpec external definitions from lexicon documents 24 + */ 25 + function generateExternalsCode(lexicons: Map<string, LexiconDoc>): string { 26 + const lines: string[] = []; 27 + 28 + lines.push('import "@typelex/emitter";'); 29 + lines.push(""); 30 + lines.push("// Generated by typelex"); 31 + lines.push("// This file is auto-generated. Do not edit manually."); 32 + lines.push(""); 33 + 34 + // Sort namespaces for consistent output 35 + const sortedNamespaces = Array.from(lexicons.entries()).sort(([a], [b]) => 36 + a.localeCompare(b) 37 + ); 38 + 39 + for (const [nsid, lexicon] of sortedNamespaces) { 40 + lines.push("@external"); 41 + // Escape reserved keywords in namespace (like 'record') 42 + const escapedNsid = nsid.replace(/\b(record|union|enum|interface|namespace|model|op|import|using|extends|is|scalar|alias|if|else|return|void|never|unknown|any|true|false|null)\b/g, '`$1`'); 43 + lines.push(`namespace ${escapedNsid} {`); 44 + 45 + // Sort definitions for consistent output 46 + const sortedDefs = Object.entries(lexicon.defs).sort(([a], [b]) => 47 + a.localeCompare(b) 48 + ); 49 + 50 + for (const [defName, def] of sortedDefs) { 51 + if (!isModelDef(def)) { 52 + continue; 53 + } 54 + 55 + const modelName = toPascalCase(defName); 56 + const isToken = isTokenDef(def); 57 + 58 + if (isToken) { 59 + lines.push(` @token model ${modelName} { }`); 60 + } else { 61 + lines.push(` model ${modelName} { }`); 62 + } 63 + } 64 + 65 + lines.push("}"); 66 + lines.push(""); 67 + } 68 + 69 + return lines.join("\n"); 70 + } 71 + 72 + /** 73 + * Generate externals.tsp file for the given namespace pattern 74 + */ 75 + export async function generateExternalsFile( 76 + namespacePattern: string, 77 + cwd: string, 78 + outDir: string = "./lexicons" 79 + ): Promise<void> { 80 + try { 81 + const prefix = getNamespacePrefix(namespacePattern); 82 + const lexiconsDir = resolve(cwd, outDir); 83 + const outputFile = resolve(cwd, "typelex/externals.tsp"); 84 + 85 + const externals = await findExternalLexicons(lexiconsDir, prefix); 86 + 87 + if (externals.size === 0) { 88 + // No externals, create empty file 89 + await mkdir(resolve(cwd, "typelex"), { recursive: true }); 90 + await writeFile( 91 + outputFile, 92 + 'import "@typelex/emitter";\n\n// Generated by typelex\n// No external lexicons found\n', 93 + "utf-8" 94 + ); 95 + return; 96 + } 97 + 98 + const code = generateExternalsCode(externals); 99 + await mkdir(resolve(cwd, "typelex"), { recursive: true }); 100 + await writeFile(outputFile, code, "utf-8"); 101 + } catch (error) { 102 + // Re-throw with better context 103 + throw new Error(`Failed to generate externals: ${error instanceof Error ? error.message : String(error)}`); 104 + } 105 + }
+78
packages/cli/src/utils/lexicon.ts
···
··· 1 + import { readFile } from "fs/promises"; 2 + import { resolve } from "path"; 3 + import { globby } from "globby"; 4 + 5 + export interface LexiconDef { 6 + type: string; 7 + [key: string]: unknown; 8 + } 9 + 10 + export interface LexiconDoc { 11 + lexicon: number; 12 + id: string; 13 + defs: Record<string, LexiconDef>; 14 + } 15 + 16 + /** 17 + * Read and parse a lexicon JSON file 18 + */ 19 + export async function readLexicon(path: string): Promise<LexiconDoc> { 20 + const content = await readFile(path, "utf-8"); 21 + return JSON.parse(content); 22 + } 23 + 24 + /** 25 + * Find all lexicon files in a directory 26 + */ 27 + export async function findLexicons(dir: string): Promise<string[]> { 28 + try { 29 + const pattern = resolve(dir, "**/*.json"); 30 + return await globby(pattern); 31 + } catch { 32 + // If directory doesn't exist, return empty array 33 + return []; 34 + } 35 + } 36 + 37 + /** 38 + * Extract external lexicons that don't match the given namespace 39 + */ 40 + export async function findExternalLexicons( 41 + lexiconsDir: string, 42 + primaryNamespace: string 43 + ): Promise<Map<string, LexiconDoc>> { 44 + const files = await findLexicons(lexiconsDir); 45 + const externals = new Map<string, LexiconDoc>(); 46 + 47 + for (const file of files) { 48 + const lexicon = await readLexicon(file); 49 + if (!lexicon.id.startsWith(primaryNamespace)) { 50 + externals.set(lexicon.id, lexicon); 51 + } 52 + } 53 + 54 + return externals; 55 + } 56 + 57 + /** 58 + * Check if a definition is a token type 59 + */ 60 + export function isTokenDef(def: LexiconDef): boolean { 61 + return def.type === "token"; 62 + } 63 + 64 + /** 65 + * Check if a definition should become a model in TypeSpec 66 + */ 67 + export function isModelDef(def: LexiconDef): boolean { 68 + const type = def.type; 69 + return ( 70 + type === "object" || 71 + type === "token" || 72 + type === "record" || 73 + type === "union" || 74 + type === "string" || 75 + type === "bytes" || 76 + type === "cid-link" 77 + ); 78 + }
+20
packages/cli/tsconfig.json
···
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "Node16", 5 + "moduleResolution": "Node16", 6 + "lib": ["ES2022"], 7 + "outDir": "dist", 8 + "rootDir": "src", 9 + "declaration": true, 10 + "declarationMap": true, 11 + "sourceMap": true, 12 + "strict": true, 13 + "esModuleInterop": true, 14 + "skipLibCheck": true, 15 + "forceConsistentCasingInFileNames": true, 16 + "resolveJsonModule": true 17 + }, 18 + "include": ["src/**/*"], 19 + "exclude": ["node_modules", "dist"] 20 + }
+4
packages/cli/typelex/externals.tsp
···
··· 1 + import "@typelex/emitter"; 2 + 3 + // Generated by typelex 4 + // No external lexicons found
+15
packages/emitter/lib/decorators.tsp
··· 1 import "../dist/tsp-index.js"; 2 3 /** 4 * Specifies the maximum number of graphemes (user-perceived characters) allowed. 5 * Used alongside maxLength for proper Unicode text handling. ··· 159 * ``` 160 */ 161 extern dec errors(target: unknown, ...errors: unknown[]);
··· 1 import "../dist/tsp-index.js"; 2 3 + using TypeSpec.Reflection; 4 + 5 /** 6 * Specifies the maximum number of graphemes (user-perceived characters) allowed. 7 * Used alongside maxLength for proper Unicode text handling. ··· 161 * ``` 162 */ 163 extern dec errors(target: unknown, ...errors: unknown[]); 164 + 165 + /** 166 + * Marks a namespace as external, preventing it from emitting JSON output. 167 + * This decorator can only be applied to namespaces. 168 + * Useful for importing definitions from other lexicons without re-emitting them. 169 + * 170 + * @example 171 + * ```typespec 172 + * @external 173 + * namespace com.atproto.repo.defs; 174 + * ``` 175 + */ 176 + extern dec external(target: Namespace);
+3 -2
packages/emitter/package.json
··· 1 { 2 "name": "@typelex/emitter", 3 - "version": "0.1.4", 4 "description": "TypeSpec emitter for ATProto Lexicon definitions", 5 "main": "dist/index.js", 6 "type": "module", ··· 27 "test:ci": "npm run build && vitest run", 28 "test:watch": "tsc-watch --onSuccess \"vitest run\"", 29 "clean": "rm -rf dist", 30 - "watch": "tsc --watch" 31 }, 32 "keywords": [ 33 "typespec",
··· 1 { 2 "name": "@typelex/emitter", 3 + "version": "0.2.0", 4 "description": "TypeSpec emitter for ATProto Lexicon definitions", 5 "main": "dist/index.js", 6 "type": "module", ··· 27 "test:ci": "npm run build && vitest run", 28 "test:watch": "tsc-watch --onSuccess \"vitest run\"", 29 "clean": "rm -rf dist", 30 + "watch": "tsc --watch", 31 + "prepublishOnly": "npm test" 32 }, 33 "keywords": [ 34 "typespec",
+23
packages/emitter/src/decorators.ts
··· 24 const inlineKey = Symbol("inline"); 25 const maxBytesKey = Symbol("maxBytes"); 26 const minBytesKey = Symbol("minBytes"); 27 28 /** 29 * @maxBytes decorator for maximum length of bytes type ··· 294 export function isReadOnly(program: Program, target: Type): boolean { 295 return program.stateSet(readOnlyKey).has(target); 296 }
··· 24 const inlineKey = Symbol("inline"); 25 const maxBytesKey = Symbol("maxBytes"); 26 const minBytesKey = Symbol("minBytes"); 27 + const externalKey = Symbol("external"); 28 29 /** 30 * @maxBytes decorator for maximum length of bytes type ··· 295 export function isReadOnly(program: Program, target: Type): boolean { 296 return program.stateSet(readOnlyKey).has(target); 297 } 298 + 299 + /** 300 + * @external decorator for marking a namespace as external 301 + * External namespaces are skipped during emission and don't produce JSON files 302 + */ 303 + export function $external(context: DecoratorContext, target: Type) { 304 + if (target.kind !== "Namespace") { 305 + context.program.reportDiagnostic({ 306 + code: "external-not-on-namespace", 307 + severity: "error", 308 + message: "@external decorator can only be applied to namespaces", 309 + target: target, 310 + }); 311 + return; 312 + } 313 + 314 + context.program.stateSet(externalKey).add(target); 315 + } 316 + 317 + export function isExternal(program: Program, target: Type): boolean { 318 + return program.stateSet(externalKey).has(target); 319 + }
+86 -15
packages/emitter/src/emitter.ts
··· 67 isInline, 68 getMaxBytes, 69 getMinBytes, 70 } from "./decorators.js"; 71 72 export interface EmitterOptions { ··· 117 if (!fullName || fullName.startsWith("TypeSpec")) { 118 for (const [_, childNs] of ns.namespaces) { 119 this.processNamespace(childNs); 120 } 121 return; 122 } ··· 369 return; 370 } 371 372 - // Only string enums can be added as defs 373 // Union refs (type: "union") must be inlined at usage sites 374 if (unionDef.type === "string" && (unionDef.knownValues || unionDef.enum)) { 375 const defName = name.charAt(0).toLowerCase() + name.slice(1); ··· 380 code: "union-refs-not-allowed-as-def", 381 severity: "error", 382 message: 383 - `Named unions of model references cannot be defined as standalone defs. ` + 384 - `Use @inline to inline them at usage sites, or use string enums instead.`, 385 target: union, 386 }); 387 } ··· 461 private unionToLexiconProperty( 462 unionType: Union, 463 prop?: ModelProperty, 464 ): LexObjectProperty | null { 465 const variants = this.parseUnionVariants(unionType); 466 ··· 503 (variants.stringLiterals.length > 0 && 504 !variants.hasStringType && 505 variants.unionRefs.length === 0 && 506 isClosed(this.program, unionType)) 507 ) { 508 const isClosedUnion = isClosed(this.program, unionType); ··· 514 const minLength = getMinLength(this.program, unionType); 515 const maxGraphemes = getMaxGraphemes(this.program, unionType); 516 const minGraphemes = getMinGraphemes(this.program, unionType); 517 return { 518 type: "string", 519 - [isClosedUnion ? "enum" : "knownValues"]: variants.stringLiterals, 520 ...(propDesc && { description: propDesc }), 521 ...(defaultValue !== undefined && 522 typeof defaultValue === "string" && { default: defaultValue }), ··· 529 530 // Model reference union (including empty union with unknown) 531 if (variants.unionRefs.length > 0 || variants.hasUnknown) { 532 - if (variants.stringLiterals.length > 0) { 533 this.program.reportDiagnostic({ 534 code: "union-mixed-refs-literals", 535 severity: "error", 536 message: 537 - `Union contains both model references and string literals. Lexicon unions must be either: ` + 538 - `(1) model references only (type: "union"), ` + 539 - `(2) string literals + string type (type: "string" with knownValues), or ` + 540 `(3) integer literals + integer type (type: "integer" with knownValues). ` + 541 `Separate these into distinct fields or nested unions.`, 542 target: unionType, ··· 609 const stringLiterals: string[] = []; 610 const numericLiterals: number[] = []; 611 const booleanLiterals: boolean[] = []; 612 let hasStringType = false; 613 let hasUnknown = false; 614 615 for (const variant of unionType.variants.values()) { 616 switch (variant.type.kind) { 617 case "Model": 618 - const ref = this.getModelReference(variant.type as Model); 619 - if (ref) unionRefs.push(ref); 620 break; 621 case "String": 622 stringLiterals.push((variant.type as StringLiteral).value); ··· 641 } 642 } 643 644 const isStringEnum = 645 - stringLiterals.length > 0 && hasStringType && unionRefs.length === 0; 646 647 return { 648 unionRefs, 649 stringLiterals, 650 numericLiterals, 651 booleanLiterals, 652 hasStringType, 653 hasUnknown, 654 isStringEnum, ··· 1194 } 1195 } 1196 1197 - const unionDef = this.unionToLexiconProperty(unionType, prop); 1198 if (!unionDef) return null; 1199 1200 // Inherit description from union if no prop description and union is @inline ··· 1395 entity: Model | Union, 1396 name: string | undefined, 1397 namespace: Namespace | undefined, 1398 ): string | null { 1399 if (!name || !namespace || namespace.name === "TypeSpec") return null; 1400 ··· 1414 }); 1415 } 1416 1417 - // Local reference (same namespace) 1418 if ( 1419 this.currentLexiconId === namespaceName || 1420 this.currentLexiconId === `${namespaceName}.defs` ··· 1427 return namespaceName; 1428 } 1429 1430 return `${namespaceName}#${defName}`; 1431 } 1432 1433 - private getModelReference(model: Model): string | null { 1434 - return this.getReference(model, model.name, model.namespace); 1435 } 1436 1437 private getUnionReference(union: Union): string | null {
··· 67 isInline, 68 getMaxBytes, 69 getMinBytes, 70 + isExternal, 71 } from "./decorators.js"; 72 73 export interface EmitterOptions { ··· 118 if (!fullName || fullName.startsWith("TypeSpec")) { 119 for (const [_, childNs] of ns.namespaces) { 120 this.processNamespace(childNs); 121 + } 122 + return; 123 + } 124 + 125 + // Skip external namespaces - they don't emit JSON files 126 + if (isExternal(this.program, ns)) { 127 + // Validate that all models in external namespaces are empty (stub-only) 128 + for (const [_, model] of ns.models) { 129 + if (model.properties && model.properties.size > 0) { 130 + this.program.reportDiagnostic({ 131 + code: "external-model-not-empty", 132 + severity: "error", 133 + message: `Models in @external namespaces must be empty stubs. Model '${model.name}' in namespace '${fullName}' has properties.`, 134 + target: model, 135 + }); 136 + } 137 } 138 return; 139 } ··· 386 return; 387 } 388 389 + // Only string enums (including token refs) can be added as defs 390 // Union refs (type: "union") must be inlined at usage sites 391 if (unionDef.type === "string" && (unionDef.knownValues || unionDef.enum)) { 392 const defName = name.charAt(0).toLowerCase() + name.slice(1); ··· 397 code: "union-refs-not-allowed-as-def", 398 severity: "error", 399 message: 400 + `Named unions of non-token model references cannot be defined as standalone defs. ` + 401 + `Use @inline to inline them at usage sites, use @token models for known values, or use string literals.`, 402 target: union, 403 }); 404 } ··· 478 private unionToLexiconProperty( 479 unionType: Union, 480 prop?: ModelProperty, 481 + isDefining?: boolean, 482 ): LexObjectProperty | null { 483 const variants = this.parseUnionVariants(unionType); 484 ··· 521 (variants.stringLiterals.length > 0 && 522 !variants.hasStringType && 523 variants.unionRefs.length === 0 && 524 + variants.knownValueRefs.length === 0 && 525 isClosed(this.program, unionType)) 526 ) { 527 const isClosedUnion = isClosed(this.program, unionType); ··· 533 const minLength = getMinLength(this.program, unionType); 534 const maxGraphemes = getMaxGraphemes(this.program, unionType); 535 const minGraphemes = getMinGraphemes(this.program, unionType); 536 + 537 + // Combine string literals and token refs for known values 538 + const allKnownValues = [ 539 + ...variants.stringLiterals, 540 + ...variants.knownValueRefs, 541 + ]; 542 + 543 return { 544 type: "string", 545 + [isClosedUnion ? "enum" : "knownValues"]: allKnownValues, 546 ...(propDesc && { description: propDesc }), 547 ...(defaultValue !== undefined && 548 typeof defaultValue === "string" && { default: defaultValue }), ··· 555 556 // Model reference union (including empty union with unknown) 557 if (variants.unionRefs.length > 0 || variants.hasUnknown) { 558 + if ( 559 + variants.stringLiterals.length > 0 || 560 + variants.knownValueRefs.length > 0 561 + ) { 562 this.program.reportDiagnostic({ 563 code: "union-mixed-refs-literals", 564 severity: "error", 565 message: 566 + `Union contains both non-token model references and string literals/token refs. Lexicon unions must be either: ` + 567 + `(1) non-token model references only (type: "union"), ` + 568 + `(2) token refs + string literals + string type (type: "string" with knownValues), or ` + 569 `(3) integer literals + integer type (type: "integer" with knownValues). ` + 570 `Separate these into distinct fields or nested unions.`, 571 target: unionType, ··· 638 const stringLiterals: string[] = []; 639 const numericLiterals: number[] = []; 640 const booleanLiterals: boolean[] = []; 641 + const tokenModels: Model[] = []; 642 let hasStringType = false; 643 let hasUnknown = false; 644 645 for (const variant of unionType.variants.values()) { 646 switch (variant.type.kind) { 647 case "Model": 648 + const model = variant.type as Model; 649 + // Collect token models separately - they're treated differently based on hasStringType 650 + if (isToken(this.program, model)) { 651 + tokenModels.push(model); 652 + } else { 653 + const ref = this.getModelReference(model); 654 + if (ref) unionRefs.push(ref); 655 + } 656 break; 657 case "String": 658 stringLiterals.push((variant.type as StringLiteral).value); ··· 677 } 678 } 679 680 + // Validate: tokens must appear with | string 681 + // Per Lexicon spec line 240: "unions can not reference token" 682 + if (tokenModels.length > 0 && !hasStringType) { 683 + this.program.reportDiagnostic({ 684 + code: "tokens-require-string", 685 + severity: "error", 686 + message: 687 + "Tokens must be used with | string. Per Lexicon spec, tokens encode as string values and cannot appear in union refs.", 688 + target: unionType, 689 + }); 690 + } 691 + 692 + // Token models become "known values" (always fully qualified refs) 693 + const knownValueRefs = tokenModels 694 + .map((m) => this.getModelReference(m, true)) 695 + .filter((ref): ref is string => ref !== null); 696 + 697 const isStringEnum = 698 + (stringLiterals.length > 0 || knownValueRefs.length > 0) && 699 + hasStringType && 700 + unionRefs.length === 0; 701 702 return { 703 unionRefs, 704 stringLiterals, 705 numericLiterals, 706 booleanLiterals, 707 + knownValueRefs, 708 hasStringType, 709 hasUnknown, 710 isStringEnum, ··· 1250 } 1251 } 1252 1253 + const unionDef = this.unionToLexiconProperty(unionType, prop, isDefining); 1254 if (!unionDef) return null; 1255 1256 // Inherit description from union if no prop description and union is @inline ··· 1451 entity: Model | Union, 1452 name: string | undefined, 1453 namespace: Namespace | undefined, 1454 + fullyQualified = false, 1455 ): string | null { 1456 if (!name || !namespace || namespace.name === "TypeSpec") return null; 1457 ··· 1471 }); 1472 } 1473 1474 + // For knownValues (fullyQualified=true), always use fully qualified refs 1475 + if (fullyQualified) { 1476 + return `${namespaceName}#${defName}`; 1477 + } 1478 + 1479 + // Local reference (same namespace) - use short ref 1480 if ( 1481 this.currentLexiconId === namespaceName || 1482 this.currentLexiconId === `${namespaceName}.defs` ··· 1489 return namespaceName; 1490 } 1491 1492 + // All other refs use fully qualified format 1493 return `${namespaceName}#${defName}`; 1494 } 1495 1496 + private getModelReference( 1497 + model: Model, 1498 + fullyQualified = false, 1499 + ): string | null { 1500 + return this.getReference( 1501 + model, 1502 + model.name, 1503 + model.namespace, 1504 + fullyQualified, 1505 + ); 1506 } 1507 1508 private getUnionReference(union: Union): string | null {
+2
packages/emitter/src/tsp-index.ts
··· 14 $inline, 15 $maxBytes, 16 $minBytes, 17 } from "./decorators.js"; 18 19 /** @internal */ ··· 34 inline: $inline, 35 maxBytes: $maxBytes, 36 minBytes: $minBytes, 37 }, 38 };
··· 14 $inline, 15 $maxBytes, 16 $minBytes, 17 + $external, 18 } from "./decorators.js"; 19 20 /** @internal */ ··· 35 inline: $inline, 36 maxBytes: $maxBytes, 37 minBytes: $minBytes, 38 + external: $external, 39 }, 40 };
+46
packages/emitter/test/integration/atproto/input/app/bsky/actor/defs.tsp
··· 372 isActive?: boolean; 373 } 374 }
··· 372 isActive?: boolean; 373 } 374 } 375 + 376 + // --- Externals --- 377 + 378 + @external 379 + namespace com.atproto.label.defs { 380 + model Label { } 381 + } 382 + 383 + @external 384 + namespace app.bsky.graph.defs { 385 + model StarterPackViewBasic { } 386 + model ListViewBasic { } 387 + } 388 + 389 + @external 390 + namespace com.atproto.repo.strongRef { 391 + model Main { } 392 + } 393 + 394 + @external 395 + namespace app.bsky.notification.defs { 396 + model ActivitySubscription { } 397 + } 398 + 399 + @external 400 + namespace app.bsky.feed.threadgate { 401 + model MentionRule { } 402 + model FollowerRule { } 403 + model FollowingRule { } 404 + model ListRule { } 405 + } 406 + 407 + @external 408 + namespace app.bsky.feed.postgate { 409 + model DisableRule { } 410 + } 411 + 412 + @external 413 + namespace app.bsky.actor.status { 414 + @token model Live { } 415 + } 416 + 417 + @external 418 + namespace app.bsky.embed.external { 419 + model View { } 420 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/getPreferences.tsp
··· 7 @required preferences: app.bsky.actor.defs.Preferences; 8 }; 9 }
··· 7 @required preferences: app.bsky.actor.defs.Preferences; 8 }; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace app.bsky.actor.defs { 15 + model Preferences { } 16 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/getProfile.tsp
··· 8 @required actor: atIdentifier 9 ): app.bsky.actor.defs.ProfileViewDetailed; 10 }
··· 8 @required actor: atIdentifier 9 ): app.bsky.actor.defs.ProfileViewDetailed; 10 } 11 + 12 + // --- Externals --- 13 + 14 + @external 15 + namespace app.bsky.actor.defs { 16 + model ProfileViewDetailed { } 17 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/getProfiles.tsp
··· 10 @required profiles: app.bsky.actor.defs.ProfileViewDetailed[]; 11 }; 12 }
··· 10 @required profiles: app.bsky.actor.defs.ProfileViewDetailed[]; 11 }; 12 } 13 + 14 + // --- Externals --- 15 + 16 + @external 17 + namespace app.bsky.actor.defs { 18 + model ProfileViewDetailed { } 19 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/getSuggestions.tsp
··· 19 recId?: int32; 20 }; 21 }
··· 19 recId?: int32; 20 }; 21 } 22 + 23 + // --- Externals --- 24 + 25 + @external 26 + namespace app.bsky.actor.defs { 27 + model ProfileView { } 28 + }
+12
packages/emitter/test/integration/atproto/input/app/bsky/actor/profile.tsp
··· 34 createdAt?: datetime; 35 } 36 }
··· 34 createdAt?: datetime; 35 } 36 } 37 + 38 + // --- Externals --- 39 + 40 + @external 41 + namespace com.atproto.label.defs { 42 + model SelfLabels { } 43 + } 44 + 45 + @external 46 + namespace com.atproto.repo.strongRef { 47 + model Main { } 48 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/putPreferences.tsp
··· 7 @required preferences: app.bsky.actor.defs.Preferences; 8 }): void; 9 }
··· 7 @required preferences: app.bsky.actor.defs.Preferences; 8 }): void; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace app.bsky.actor.defs { 15 + model Preferences { } 16 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/searchActors.tsp
··· 20 @required actors: app.bsky.actor.defs.ProfileView[]; 21 }; 22 }
··· 20 @required actors: app.bsky.actor.defs.ProfileView[]; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace app.bsky.actor.defs { 28 + model ProfileView { } 29 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/searchActorsTypeahead.tsp
··· 17 @required actors: app.bsky.actor.defs.ProfileViewBasic[]; 18 }; 19 }
··· 17 @required actors: app.bsky.actor.defs.ProfileViewBasic[]; 18 }; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace app.bsky.actor.defs { 25 + model ProfileViewBasic { } 26 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/actor/status.tsp
··· 22 @token 23 model Live {} 24 }
··· 22 @token 23 model Live {} 24 } 25 + 26 + // --- Externals --- 27 + 28 + @external 29 + namespace app.bsky.embed.external { 30 + model Main { } 31 + }
+14
packages/emitter/test/integration/atproto/input/app/bsky/bookmark/defs.tsp
··· 24 ); 25 } 26 }
··· 24 ); 25 } 26 } 27 + 28 + // --- Externals --- 29 + 30 + @external 31 + namespace com.atproto.repo.strongRef { 32 + model Main { } 33 + } 34 + 35 + @external 36 + namespace app.bsky.feed.defs { 37 + model BlockedPost { } 38 + model NotFoundPost { } 39 + model PostView { } 40 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/bookmark/getBookmarks.tsp
··· 14 @required bookmarks: app.bsky.bookmark.defs.BookmarkView[]; 15 }; 16 }
··· 14 @required bookmarks: app.bsky.bookmark.defs.BookmarkView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.bookmark.defs { 22 + model BookmarkView { } 23 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/embed/images.tsp
··· 40 aspectRatio?: app.bsky.embed.defs.AspectRatio; 41 } 42 }
··· 40 aspectRatio?: app.bsky.embed.defs.AspectRatio; 41 } 42 } 43 + 44 + // --- Externals --- 45 + 46 + @external 47 + namespace app.bsky.embed.defs { 48 + model AspectRatio { } 49 + }
+54
packages/emitter/test/integration/atproto/input/app/bsky/embed/record.tsp
··· 74 detached: boolean = true; 75 } 76 }
··· 74 detached: boolean = true; 75 } 76 } 77 + 78 + // --- Externals --- 79 + 80 + @external 81 + namespace com.atproto.repo.strongRef { 82 + model Main { } 83 + } 84 + 85 + @external 86 + namespace app.bsky.feed.defs { 87 + model GeneratorView { } 88 + model BlockedAuthor { } 89 + } 90 + 91 + @external 92 + namespace app.bsky.graph.defs { 93 + model ListView { } 94 + model StarterPackViewBasic { } 95 + } 96 + 97 + @external 98 + namespace app.bsky.labeler.defs { 99 + model LabelerView { } 100 + } 101 + 102 + @external 103 + namespace app.bsky.actor.defs { 104 + model ProfileViewBasic { } 105 + } 106 + 107 + @external 108 + namespace com.atproto.label.defs { 109 + model Label { } 110 + } 111 + 112 + @external 113 + namespace app.bsky.embed.images { 114 + model View { } 115 + } 116 + 117 + @external 118 + namespace app.bsky.embed.video { 119 + model View { } 120 + } 121 + 122 + @external 123 + namespace app.bsky.embed.external { 124 + model View { } 125 + } 126 + 127 + @external 128 + namespace app.bsky.embed.recordWithMedia { 129 + model View { } 130 + }
+26
packages/emitter/test/integration/atproto/input/app/bsky/embed/recordWithMedia.tsp
··· 26 ); 27 } 28 }
··· 26 ); 27 } 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace app.bsky.embed.`record` { 34 + model Main { } 35 + model View { } 36 + } 37 + 38 + @external 39 + namespace app.bsky.embed.images { 40 + model Main { } 41 + model View { } 42 + } 43 + 44 + @external 45 + namespace app.bsky.embed.video { 46 + model Main { } 47 + model View { } 48 + } 49 + 50 + @external 51 + namespace app.bsky.embed.external { 52 + model Main { } 53 + model View { } 54 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/embed/video.tsp
··· 36 aspectRatio?: app.bsky.embed.defs.AspectRatio; 37 } 38 }
··· 36 aspectRatio?: app.bsky.embed.defs.AspectRatio; 37 } 38 } 39 + 40 + // --- Externals --- 41 + 42 + @external 43 + namespace app.bsky.embed.defs { 44 + model AspectRatio { } 45 + }
+49
packages/emitter/test/integration/atproto/input/app/bsky/feed/defs.tsp
··· 246 @token 247 model InteractionShare {} 248 }
··· 246 @token 247 model InteractionShare {} 248 } 249 + 250 + // --- Externals --- 251 + 252 + @external 253 + namespace app.bsky.actor.defs { 254 + model ProfileViewBasic { } 255 + model ViewerState { } 256 + model ProfileView { } 257 + } 258 + 259 + @external 260 + namespace app.bsky.embed.images { 261 + model View { } 262 + } 263 + 264 + @external 265 + namespace app.bsky.embed.video { 266 + model View { } 267 + } 268 + 269 + @external 270 + namespace app.bsky.embed.external { 271 + model View { } 272 + } 273 + 274 + @external 275 + namespace app.bsky.embed.`record` { 276 + model View { } 277 + } 278 + 279 + @external 280 + namespace app.bsky.embed.recordWithMedia { 281 + model View { } 282 + } 283 + 284 + @external 285 + namespace com.atproto.label.defs { 286 + model Label { } 287 + } 288 + 289 + @external 290 + namespace app.bsky.richtext.facet { 291 + model Main { } 292 + } 293 + 294 + @external 295 + namespace app.bsky.graph.defs { 296 + model ListViewBasic { } 297 + }
+18
packages/emitter/test/integration/atproto/input/app/bsky/feed/generator.tsp
··· 30 @required createdAt: datetime; 31 } 32 }
··· 30 @required createdAt: datetime; 31 } 32 } 33 + 34 + // --- Externals --- 35 + 36 + @external 37 + namespace app.bsky.richtext.facet { 38 + model Main { } 39 + } 40 + 41 + @external 42 + namespace com.atproto.label.defs { 43 + model SelfLabels { } 44 + } 45 + 46 + @external 47 + namespace app.bsky.feed.defs { 48 + @token model ContentModeUnspecified { } 49 + @token model ContentModeVideo { } 50 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getActorFeeds.tsp
··· 16 @required feeds: app.bsky.feed.defs.GeneratorView[]; 17 }; 18 }
··· 16 @required feeds: app.bsky.feed.defs.GeneratorView[]; 17 }; 18 } 19 + 20 + // --- Externals --- 21 + 22 + @external 23 + namespace app.bsky.feed.defs { 24 + model GeneratorView { } 25 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getActorLikes.tsp
··· 20 @required feed: app.bsky.feed.defs.FeedViewPost[]; 21 }; 22 }
··· 20 @required feed: app.bsky.feed.defs.FeedViewPost[]; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace app.bsky.feed.defs { 28 + model FeedViewPost { } 29 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getAuthorFeed.tsp
··· 25 @required feed: app.bsky.feed.defs.FeedViewPost[]; 26 }; 27 }
··· 25 @required feed: app.bsky.feed.defs.FeedViewPost[]; 26 }; 27 } 28 + 29 + // --- Externals --- 30 + 31 + @external 32 + namespace app.bsky.feed.defs { 33 + model FeedViewPost { } 34 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getFeed.tsp
··· 19 @required feed: app.bsky.feed.defs.FeedViewPost[]; 20 }; 21 }
··· 19 @required feed: app.bsky.feed.defs.FeedViewPost[]; 20 }; 21 } 22 + 23 + // --- Externals --- 24 + 25 + @external 26 + namespace app.bsky.feed.defs { 27 + model FeedViewPost { } 28 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getFeedGenerator.tsp
··· 18 isValid: boolean; 19 }; 20 }
··· 18 isValid: boolean; 19 }; 20 } 21 + 22 + // --- Externals --- 23 + 24 + @external 25 + namespace app.bsky.feed.defs { 26 + model GeneratorView { } 27 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getFeedGenerators.tsp
··· 9 @required feeds: app.bsky.feed.defs.GeneratorView[]; 10 }; 11 }
··· 9 @required feeds: app.bsky.feed.defs.GeneratorView[]; 10 }; 11 } 12 + 13 + // --- Externals --- 14 + 15 + @external 16 + namespace app.bsky.feed.defs { 17 + model GeneratorView { } 18 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getFeedSkeleton.tsp
··· 26 reqId?: string; 27 }; 28 }
··· 26 reqId?: string; 27 }; 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace app.bsky.feed.defs { 34 + model SkeletonFeedPost { } 35 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getLikes.tsp
··· 28 @required actor: app.bsky.actor.defs.ProfileView; 29 } 30 }
··· 28 @required actor: app.bsky.actor.defs.ProfileView; 29 } 30 } 31 + 32 + // --- Externals --- 33 + 34 + @external 35 + namespace app.bsky.actor.defs { 36 + model ProfileView { } 37 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getListFeed.tsp
··· 20 @required feed: app.bsky.feed.defs.FeedViewPost[]; 21 }; 22 }
··· 20 @required feed: app.bsky.feed.defs.FeedViewPost[]; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace app.bsky.feed.defs { 28 + model FeedViewPost { } 29 + }
+10
packages/emitter/test/integration/atproto/input/app/bsky/feed/getPostThread.tsp
··· 26 threadgate?: app.bsky.feed.defs.ThreadgateView; 27 }; 28 }
··· 26 threadgate?: app.bsky.feed.defs.ThreadgateView; 27 }; 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace app.bsky.feed.defs { 34 + model ThreadViewPost { } 35 + model NotFoundPost { } 36 + model BlockedPost { } 37 + model ThreadgateView { } 38 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getPosts.tsp
··· 11 @required posts: app.bsky.feed.defs.PostView[]; 12 }; 13 }
··· 11 @required posts: app.bsky.feed.defs.PostView[]; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace app.bsky.feed.defs { 19 + model PostView { } 20 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getQuotes.tsp
··· 22 @required posts: app.bsky.feed.defs.PostView[]; 23 }; 24 }
··· 22 @required posts: app.bsky.feed.defs.PostView[]; 23 }; 24 } 25 + 26 + // --- Externals --- 27 + 28 + @external 29 + namespace app.bsky.feed.defs { 30 + model PostView { } 31 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getRepostedBy.tsp
··· 22 @required repostedBy: app.bsky.actor.defs.ProfileView[]; 23 }; 24 }
··· 22 @required repostedBy: app.bsky.actor.defs.ProfileView[]; 23 }; 24 } 25 + 26 + // --- Externals --- 27 + 28 + @external 29 + namespace app.bsky.actor.defs { 30 + model ProfileView { } 31 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getSuggestedFeeds.tsp
··· 14 @required feeds: app.bsky.feed.defs.GeneratorView[]; 15 }; 16 }
··· 14 @required feeds: app.bsky.feed.defs.GeneratorView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.feed.defs { 22 + model GeneratorView { } 23 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/getTimeline.tsp
··· 17 @required feed: app.bsky.feed.defs.FeedViewPost[]; 18 }; 19 }
··· 17 @required feed: app.bsky.feed.defs.FeedViewPost[]; 18 }; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace app.bsky.feed.defs { 25 + model FeedViewPost { } 26 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/like.tsp
··· 13 via?: com.atproto.repo.strongRef.Main; 14 } 15 }
··· 13 via?: com.atproto.repo.strongRef.Main; 14 } 15 } 16 + 17 + // --- Externals --- 18 + 19 + @external 20 + namespace com.atproto.repo.strongRef { 21 + model Main { } 22 + }
+42
packages/emitter/test/integration/atproto/input/app/bsky/feed/post.tsp
··· 74 @maxGraphemes(64) 75 @maxLength(640) 76 scalar PostTag extends string;
··· 74 @maxGraphemes(64) 75 @maxLength(640) 76 scalar PostTag extends string; 77 + 78 + // --- Externals --- 79 + 80 + @external 81 + namespace app.bsky.richtext.facet { 82 + model Main { } 83 + } 84 + 85 + @external 86 + namespace app.bsky.embed.images { 87 + model Main { } 88 + } 89 + 90 + @external 91 + namespace app.bsky.embed.video { 92 + model Main { } 93 + } 94 + 95 + @external 96 + namespace app.bsky.embed.external { 97 + model Main { } 98 + } 99 + 100 + @external 101 + namespace app.bsky.embed.`record` { 102 + model Main { } 103 + } 104 + 105 + @external 106 + namespace app.bsky.embed.recordWithMedia { 107 + model Main { } 108 + } 109 + 110 + @external 111 + namespace com.atproto.label.defs { 112 + model SelfLabels { } 113 + } 114 + 115 + @external 116 + namespace com.atproto.repo.strongRef { 117 + model Main { } 118 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/repost.tsp
··· 9 via?: com.atproto.repo.strongRef.Main; 10 } 11 }
··· 9 via?: com.atproto.repo.strongRef.Main; 10 } 11 } 12 + 13 + // --- Externals --- 14 + 15 + @external 16 + namespace com.atproto.repo.strongRef { 17 + model Main { } 18 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/searchPosts.tsp
··· 56 @required posts: app.bsky.feed.defs.PostView[]; 57 }; 58 }
··· 56 @required posts: app.bsky.feed.defs.PostView[]; 57 }; 58 } 59 + 60 + // --- Externals --- 61 + 62 + @external 63 + namespace app.bsky.feed.defs { 64 + model PostView { } 65 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/feed/sendInteractions.tsp
··· 7 @required interactions: app.bsky.feed.defs.Interaction[]; 8 }): {}; 9 }
··· 7 @required interactions: app.bsky.feed.defs.Interaction[]; 8 }): {}; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace app.bsky.feed.defs { 15 + model Interaction { } 16 + }
+23
packages/emitter/test/integration/atproto/input/app/bsky/graph/defs.tsp
··· 139 followedBy?: atUri; 140 } 141 }
··· 139 followedBy?: atUri; 140 } 141 } 142 + 143 + // --- Externals --- 144 + 145 + @external 146 + namespace com.atproto.label.defs { 147 + model Label { } 148 + } 149 + 150 + @external 151 + namespace app.bsky.actor.defs { 152 + model ProfileView { } 153 + model ProfileViewBasic { } 154 + } 155 + 156 + @external 157 + namespace app.bsky.richtext.facet { 158 + model Main { } 159 + } 160 + 161 + @external 162 + namespace app.bsky.feed.defs { 163 + model GeneratorView { } 164 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getActorStarterPacks.tsp
··· 16 @required starterPacks: app.bsky.graph.defs.StarterPackViewBasic[]; 17 }; 18 }
··· 16 @required starterPacks: app.bsky.graph.defs.StarterPackViewBasic[]; 17 }; 18 } 19 + 20 + // --- Externals --- 21 + 22 + @external 23 + namespace app.bsky.graph.defs { 24 + model StarterPackViewBasic { } 25 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getBlocks.tsp
··· 14 @required blocks: app.bsky.actor.defs.ProfileView[]; 15 }; 16 }
··· 14 @required blocks: app.bsky.actor.defs.ProfileView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.actor.defs { 22 + model ProfileView { } 23 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getFollowers.tsp
··· 17 @required followers: app.bsky.actor.defs.ProfileView[]; 18 }; 19 }
··· 17 @required followers: app.bsky.actor.defs.ProfileView[]; 18 }; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace app.bsky.actor.defs { 25 + model ProfileView { } 26 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getFollows.tsp
··· 17 @required follows: app.bsky.actor.defs.ProfileView[]; 18 }; 19 }
··· 17 @required follows: app.bsky.actor.defs.ProfileView[]; 18 }; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace app.bsky.actor.defs { 25 + model ProfileView { } 26 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getKnownFollowers.tsp
··· 17 @required followers: app.bsky.actor.defs.ProfileView[]; 18 }; 19 }
··· 17 @required followers: app.bsky.actor.defs.ProfileView[]; 18 }; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace app.bsky.actor.defs { 25 + model ProfileView { } 26 + }
+8
packages/emitter/test/integration/atproto/input/app/bsky/graph/getList.tsp
··· 18 @required items: app.bsky.graph.defs.ListItemView[]; 19 }; 20 }
··· 18 @required items: app.bsky.graph.defs.ListItemView[]; 19 }; 20 } 21 + 22 + // --- Externals --- 23 + 24 + @external 25 + namespace app.bsky.graph.defs { 26 + model ListView { } 27 + model ListItemView { } 28 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getListBlocks.tsp
··· 14 @required lists: app.bsky.graph.defs.ListView[]; 15 }; 16 }
··· 14 @required lists: app.bsky.graph.defs.ListView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.graph.defs { 22 + model ListView { } 23 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getListMutes.tsp
··· 14 @required lists: app.bsky.graph.defs.ListView[]; 15 }; 16 }
··· 14 @required lists: app.bsky.graph.defs.ListView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.graph.defs { 22 + model ListView { } 23 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getLists.tsp
··· 20 @required lists: app.bsky.graph.defs.ListView[]; 21 }; 22 }
··· 20 @required lists: app.bsky.graph.defs.ListView[]; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace app.bsky.graph.defs { 28 + model ListView { } 29 + }
+8
packages/emitter/test/integration/atproto/input/app/bsky/graph/getListsWithMembership.tsp
··· 27 listItem?: app.bsky.graph.defs.ListItemView; 28 } 29 }
··· 27 listItem?: app.bsky.graph.defs.ListItemView; 28 } 29 } 30 + 31 + // --- Externals --- 32 + 33 + @external 34 + namespace app.bsky.graph.defs { 35 + model ListView { } 36 + model ListItemView { } 37 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getMutes.tsp
··· 14 @required mutes: app.bsky.actor.defs.ProfileView[]; 15 }; 16 }
··· 14 @required mutes: app.bsky.actor.defs.ProfileView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.actor.defs { 22 + model ProfileView { } 23 + }
+8
packages/emitter/test/integration/atproto/input/app/bsky/graph/getRelationships.tsp
··· 26 /** the primary actor at-identifier could not be resolved */ 27 model ActorNotFound {} 28 }
··· 26 /** the primary actor at-identifier could not be resolved */ 27 model ActorNotFound {} 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace app.bsky.graph.defs { 34 + model Relationship { } 35 + model NotFoundActor { } 36 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getStarterPack.tsp
··· 11 @required starterPack: app.bsky.graph.defs.StarterPackView; 12 }; 13 }
··· 11 @required starterPack: app.bsky.graph.defs.StarterPackView; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace app.bsky.graph.defs { 19 + model StarterPackView { } 20 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getStarterPacks.tsp
··· 11 @required starterPacks: app.bsky.graph.defs.StarterPackViewBasic[]; 12 }; 13 }
··· 11 @required starterPacks: app.bsky.graph.defs.StarterPackViewBasic[]; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace app.bsky.graph.defs { 19 + model StarterPackViewBasic { } 20 + }
+8
packages/emitter/test/integration/atproto/input/app/bsky/graph/getStarterPacksWithMembership.tsp
··· 24 listItem?: app.bsky.graph.defs.ListItemView; 25 } 26 }
··· 24 listItem?: app.bsky.graph.defs.ListItemView; 25 } 26 } 27 + 28 + // --- Externals --- 29 + 30 + @external 31 + namespace app.bsky.graph.defs { 32 + model StarterPackView { } 33 + model ListItemView { } 34 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/getSuggestedFollowsByActor.tsp
··· 16 recId?: int32; 17 }; 18 }
··· 16 recId?: int32; 17 }; 18 } 19 + 20 + // --- Externals --- 21 + 22 + @external 23 + namespace app.bsky.actor.defs { 24 + model ProfileView { } 25 + }
+17
packages/emitter/test/integration/atproto/input/app/bsky/graph/list.tsp
··· 27 @required createdAt: datetime; 28 } 29 }
··· 27 @required createdAt: datetime; 28 } 29 } 30 + 31 + // --- Externals --- 32 + 33 + @external 34 + namespace app.bsky.graph.defs { 35 + model ListPurpose { } 36 + } 37 + 38 + @external 39 + namespace app.bsky.richtext.facet { 40 + model Main { } 41 + } 42 + 43 + @external 44 + namespace com.atproto.label.defs { 45 + model SelfLabels { } 46 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/searchStarterPacks.tsp
··· 18 @required starterPacks: app.bsky.graph.defs.StarterPackViewBasic[]; 19 }; 20 }
··· 18 @required starterPacks: app.bsky.graph.defs.StarterPackViewBasic[]; 19 }; 20 } 21 + 22 + // --- Externals --- 23 + 24 + @external 25 + namespace app.bsky.graph.defs { 26 + model StarterPackViewBasic { } 27 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/graph/starterpack.tsp
··· 33 uri: atUri; 34 } 35 }
··· 33 uri: atUri; 34 } 35 } 36 + 37 + // --- Externals --- 38 + 39 + @external 40 + namespace app.bsky.richtext.facet { 41 + model Main { } 42 + }
+20
packages/emitter/test/integration/atproto/input/app/bsky/labeler/defs.tsp
··· 54 labelValueDefinitions?: com.atproto.label.defs.LabelValueDefinition[]; 55 } 56 }
··· 54 labelValueDefinitions?: com.atproto.label.defs.LabelValueDefinition[]; 55 } 56 } 57 + 58 + // --- Externals --- 59 + 60 + @external 61 + namespace app.bsky.actor.defs { 62 + model ProfileView { } 63 + } 64 + 65 + @external 66 + namespace com.atproto.label.defs { 67 + model Label { } 68 + model LabelValue { } 69 + model LabelValueDefinition { } 70 + } 71 + 72 + @external 73 + namespace com.atproto.moderation.defs { 74 + model ReasonType { } 75 + model SubjectType { } 76 + }
+8
packages/emitter/test/integration/atproto/input/app/bsky/labeler/getServices.tsp
··· 15 )[]; 16 }; 17 }
··· 15 )[]; 16 }; 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace app.bsky.labeler.defs { 23 + model LabelerView { } 24 + model LabelerViewDetailed { } 25 + }
+18
packages/emitter/test/integration/atproto/input/app/bsky/labeler/service.tsp
··· 20 subjectCollections?: nsid[]; 21 } 22 }
··· 20 subjectCollections?: nsid[]; 21 } 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace app.bsky.labeler.defs { 28 + model LabelerPolicies { } 29 + } 30 + 31 + @external 32 + namespace com.atproto.label.defs { 33 + model SelfLabels { } 34 + } 35 + 36 + @external 37 + namespace com.atproto.moderation.defs { 38 + model ReasonType { } 39 + model SubjectType { } 40 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/notification/getPreferences.tsp
··· 7 @required preferences: app.bsky.notification.defs.Preferences; 8 }; 9 }
··· 7 @required preferences: app.bsky.notification.defs.Preferences; 8 }; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace app.bsky.notification.defs { 15 + model Preferences { } 16 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/notification/listActivitySubscriptions.tsp
··· 14 @required subscriptions: app.bsky.actor.defs.ProfileView[]; 15 }; 16 }
··· 14 @required subscriptions: app.bsky.actor.defs.ProfileView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.actor.defs { 22 + model ProfileView { } 23 + }
+12
packages/emitter/test/integration/atproto/input/app/bsky/notification/listNotifications.tsp
··· 40 labels?: com.atproto.label.defs.Label[]; 41 }; 42 }
··· 40 labels?: com.atproto.label.defs.Label[]; 41 }; 42 } 43 + 44 + // --- Externals --- 45 + 46 + @external 47 + namespace app.bsky.actor.defs { 48 + model ProfileView { } 49 + } 50 + 51 + @external 52 + namespace com.atproto.label.defs { 53 + model Label { } 54 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/notification/putActivitySubscription.tsp
··· 13 activitySubscription?: app.bsky.notification.defs.ActivitySubscription; 14 }; 15 }
··· 13 activitySubscription?: app.bsky.notification.defs.ActivitySubscription; 14 }; 15 } 16 + 17 + // --- Externals --- 18 + 19 + @external 20 + namespace app.bsky.notification.defs { 21 + model ActivitySubscription { } 22 + }
+10
packages/emitter/test/integration/atproto/input/app/bsky/notification/putPreferencesV2.tsp
··· 21 @required preferences: app.bsky.notification.defs.Preferences; 22 }; 23 }
··· 21 @required preferences: app.bsky.notification.defs.Preferences; 22 }; 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace app.bsky.notification.defs { 29 + model ChatPreference { } 30 + model FilterablePreference { } 31 + model Preference { } 32 + model Preferences { } 33 + }
+13
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/defs.tsp
··· 114 completeUa?: string; 115 } 116 }
··· 114 completeUa?: string; 115 } 116 } 117 + 118 + // --- Externals --- 119 + 120 + @external 121 + namespace app.bsky.actor.defs { 122 + model ProfileViewBasic { } 123 + } 124 + 125 + @external 126 + namespace app.bsky.feed.defs { 127 + model PostView { } 128 + model BlockedAuthor { } 129 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getAgeAssuranceState.tsp
··· 5 @query 6 op main(): app.bsky.unspecced.defs.AgeAssuranceState; 7 }
··· 5 @query 6 op main(): app.bsky.unspecced.defs.AgeAssuranceState; 7 } 8 + 9 + // --- Externals --- 10 + 11 + @external 12 + namespace app.bsky.unspecced.defs { 13 + model AgeAssuranceState { } 14 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.tsp
··· 11 @required starterPacks: app.bsky.graph.defs.StarterPackView[]; 12 }; 13 }
··· 11 @required starterPacks: app.bsky.graph.defs.StarterPackView[]; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace app.bsky.graph.defs { 19 + model StarterPackView { } 20 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getPopularFeedGenerators.tsp
··· 15 @required feeds: app.bsky.feed.defs.GeneratorView[]; 16 }; 17 }
··· 15 @required feeds: app.bsky.feed.defs.GeneratorView[]; 16 }; 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace app.bsky.feed.defs { 23 + model GeneratorView { } 24 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getPostThreadOtherV2.tsp
··· 26 @required value: (app.bsky.unspecced.defs.ThreadItemPost | unknown); 27 } 28 }
··· 26 @required value: (app.bsky.unspecced.defs.ThreadItemPost | unknown); 27 } 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace app.bsky.unspecced.defs { 34 + model ThreadItemPost { } 35 + }
+15
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getPostThreadV2.tsp
··· 55 ); 56 } 57 }
··· 55 ); 56 } 57 } 58 + 59 + // --- Externals --- 60 + 61 + @external 62 + namespace app.bsky.feed.defs { 63 + model ThreadgateView { } 64 + } 65 + 66 + @external 67 + namespace app.bsky.unspecced.defs { 68 + model ThreadItemPost { } 69 + model ThreadItemNoUnauthenticated { } 70 + model ThreadItemNotFound { } 71 + model ThreadItemBlocked { } 72 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getSuggestedFeeds.tsp
··· 11 @required feeds: app.bsky.feed.defs.GeneratorView[]; 12 }; 13 }
··· 11 @required feeds: app.bsky.feed.defs.GeneratorView[]; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace app.bsky.feed.defs { 19 + model GeneratorView { } 20 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getSuggestedStarterPacks.tsp
··· 11 @required starterPacks: app.bsky.graph.defs.StarterPackView[]; 12 }; 13 }
··· 11 @required starterPacks: app.bsky.graph.defs.StarterPackView[]; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace app.bsky.graph.defs { 19 + model StarterPackView { } 20 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getSuggestedUsers.tsp
··· 14 @required actors: app.bsky.actor.defs.ProfileView[]; 15 }; 16 }
··· 14 @required actors: app.bsky.actor.defs.ProfileView[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.actor.defs { 22 + model ProfileView { } 23 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getSuggestionsSkeleton.tsp
··· 26 recId?: integer; 27 }; 28 }
··· 26 recId?: integer; 27 }; 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace app.bsky.unspecced.defs { 34 + model SkeletonSearchActor { } 35 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getTrendingTopics.tsp
··· 15 @required suggested: app.bsky.unspecced.defs.TrendingTopic[]; 16 }; 17 }
··· 15 @required suggested: app.bsky.unspecced.defs.TrendingTopic[]; 16 }; 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace app.bsky.unspecced.defs { 23 + model TrendingTopic { } 24 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getTrends.tsp
··· 11 @required trends: app.bsky.unspecced.defs.TrendView[]; 12 }; 13 }
··· 11 @required trends: app.bsky.unspecced.defs.TrendView[]; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace app.bsky.unspecced.defs { 19 + model TrendView { } 20 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/getTrendsSkeleton.tsp
··· 14 @required trends: app.bsky.unspecced.defs.SkeletonTrend[]; 15 }; 16 }
··· 14 @required trends: app.bsky.unspecced.defs.SkeletonTrend[]; 15 }; 16 } 17 + 18 + // --- Externals --- 19 + 20 + @external 21 + namespace app.bsky.unspecced.defs { 22 + model SkeletonTrend { } 23 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/initAgeAssurance.tsp
··· 22 countryCode: string; 23 }): app.bsky.unspecced.defs.AgeAssuranceState; 24 }
··· 22 countryCode: string; 23 }): app.bsky.unspecced.defs.AgeAssuranceState; 24 } 25 + 26 + // --- Externals --- 27 + 28 + @external 29 + namespace app.bsky.unspecced.defs { 30 + model AgeAssuranceState { } 31 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/searchActorsSkeleton.tsp
··· 31 @required actors: app.bsky.unspecced.defs.SkeletonSearchActor[]; 32 }; 33 }
··· 31 @required actors: app.bsky.unspecced.defs.SkeletonSearchActor[]; 32 }; 33 } 34 + 35 + // --- Externals --- 36 + 37 + @external 38 + namespace app.bsky.unspecced.defs { 39 + model SkeletonSearchActor { } 40 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/searchPostsSkeleton.tsp
··· 59 @required posts: app.bsky.unspecced.defs.SkeletonSearchPost[]; 60 }; 61 }
··· 59 @required posts: app.bsky.unspecced.defs.SkeletonSearchPost[]; 60 }; 61 } 62 + 63 + // --- Externals --- 64 + 65 + @external 66 + namespace app.bsky.unspecced.defs { 67 + model SkeletonSearchPost { } 68 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/unspecced/searchStarterPacksSkeleton.tsp
··· 28 @required starterPacks: app.bsky.unspecced.defs.SkeletonSearchStarterPack[]; 29 }; 30 }
··· 28 @required starterPacks: app.bsky.unspecced.defs.SkeletonSearchStarterPack[]; 29 }; 30 } 31 + 32 + // --- Externals --- 33 + 34 + @external 35 + namespace app.bsky.unspecced.defs { 36 + model SkeletonSearchStarterPack { } 37 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/video/getJobStatus.tsp
··· 9 @required jobStatus: app.bsky.video.defs.JobStatus; 10 }; 11 }
··· 9 @required jobStatus: app.bsky.video.defs.JobStatus; 10 }; 11 } 12 + 13 + // --- Externals --- 14 + 15 + @external 16 + namespace app.bsky.video.defs { 17 + model JobStatus { } 18 + }
+7
packages/emitter/test/integration/atproto/input/app/bsky/video/uploadVideo.tsp
··· 10 @required jobStatus: app.bsky.video.defs.JobStatus; 11 }; 12 }
··· 10 @required jobStatus: app.bsky.video.defs.JobStatus; 11 }; 12 } 13 + 14 + // --- Externals --- 15 + 16 + @external 17 + namespace app.bsky.video.defs { 18 + model JobStatus { } 19 + }
+14
packages/emitter/test/integration/atproto/input/chat/bsky/actor/defs.tsp
··· 20 verification?: app.bsky.actor.defs.VerificationState; 21 } 22 }
··· 20 verification?: app.bsky.actor.defs.VerificationState; 21 } 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace app.bsky.actor.defs { 28 + model ProfileAssociated { } 29 + model ViewerState { } 30 + model VerificationState { } 31 + } 32 + 33 + @external 34 + namespace com.atproto.label.defs { 35 + model Label { } 36 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/addReaction.tsp
··· 27 @required message: chat.bsky.convo.defs.MessageView; 28 }; 29 }
··· 27 @required message: chat.bsky.convo.defs.MessageView; 28 }; 29 } 30 + 31 + // --- Externals --- 32 + 33 + @external 34 + namespace chat.bsky.convo.defs { 35 + model MessageView { } 36 + }
+18
packages/emitter/test/integration/atproto/input/chat/bsky/convo/defs.tsp
··· 139 @required reaction: ReactionView; 140 } 141 }
··· 139 @required reaction: ReactionView; 140 } 141 } 142 + 143 + // --- Externals --- 144 + 145 + @external 146 + namespace app.bsky.richtext.facet { 147 + model Main { } 148 + } 149 + 150 + @external 151 + namespace app.bsky.embed.`record` { 152 + model Main { } 153 + model View { } 154 + } 155 + 156 + @external 157 + namespace chat.bsky.actor.defs { 158 + model ProfileViewBasic { } 159 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/deleteMessageForSelf.tsp
··· 7 @required messageId: string; 8 }): chat.bsky.convo.defs.DeletedMessageView; 9 }
··· 7 @required messageId: string; 8 }): chat.bsky.convo.defs.DeletedMessageView; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace chat.bsky.convo.defs { 15 + model DeletedMessageView { } 16 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/getConvo.tsp
··· 6 @required convo: chat.bsky.convo.defs.ConvoView; 7 }; 8 }
··· 6 @required convo: chat.bsky.convo.defs.ConvoView; 7 }; 8 } 9 + 10 + // --- Externals --- 11 + 12 + @external 13 + namespace chat.bsky.convo.defs { 14 + model ConvoView { } 15 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/getConvoAvailability.tsp
··· 13 convo?: chat.bsky.convo.defs.ConvoView; 14 }; 15 }
··· 13 convo?: chat.bsky.convo.defs.ConvoView; 14 }; 15 } 16 + 17 + // --- Externals --- 18 + 19 + @external 20 + namespace chat.bsky.convo.defs { 21 + model ConvoView { } 22 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/getConvoForMembers.tsp
··· 11 @required convo: chat.bsky.convo.defs.ConvoView; 12 }; 13 }
··· 11 @required convo: chat.bsky.convo.defs.ConvoView; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace chat.bsky.convo.defs { 19 + model ConvoView { } 20 + }
+16
packages/emitter/test/integration/atproto/input/chat/bsky/convo/getLog.tsp
··· 21 )[]; 22 }; 23 }
··· 21 )[]; 22 }; 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace chat.bsky.convo.defs { 29 + model LogBeginConvo { } 30 + model LogAcceptConvo { } 31 + model LogLeaveConvo { } 32 + model LogMuteConvo { } 33 + model LogUnmuteConvo { } 34 + model LogCreateMessage { } 35 + model LogDeleteMessage { } 36 + model LogReadMessage { } 37 + model LogAddReaction { } 38 + model LogRemoveReaction { } 39 + }
+8
packages/emitter/test/integration/atproto/input/chat/bsky/convo/getMessages.tsp
··· 21 )[]; 22 }; 23 }
··· 21 )[]; 22 }; 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace chat.bsky.convo.defs { 29 + model MessageView { } 30 + model DeletedMessageView { } 31 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/listConvos.tsp
··· 17 @required convos: chat.bsky.convo.defs.ConvoView[]; 18 }; 19 }
··· 17 @required convos: chat.bsky.convo.defs.ConvoView[]; 18 }; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace chat.bsky.convo.defs { 25 + model ConvoView { } 26 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/muteConvo.tsp
··· 8 @required convo: chat.bsky.convo.defs.ConvoView; 9 }; 10 }
··· 8 @required convo: chat.bsky.convo.defs.ConvoView; 9 }; 10 } 11 + 12 + // --- Externals --- 13 + 14 + @external 15 + namespace chat.bsky.convo.defs { 16 + model ConvoView { } 17 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/removeReaction.tsp
··· 24 @required message: chat.bsky.convo.defs.MessageView; 25 }; 26 }
··· 24 @required message: chat.bsky.convo.defs.MessageView; 25 }; 26 } 27 + 28 + // --- Externals --- 29 + 30 + @external 31 + namespace chat.bsky.convo.defs { 32 + model MessageView { } 33 + }
+8
packages/emitter/test/integration/atproto/input/chat/bsky/convo/sendMessage.tsp
··· 7 @required message: chat.bsky.convo.defs.MessageInput; 8 }): chat.bsky.convo.defs.MessageView; 9 }
··· 7 @required message: chat.bsky.convo.defs.MessageInput; 8 }): chat.bsky.convo.defs.MessageView; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace chat.bsky.convo.defs { 15 + model MessageInput { } 16 + model MessageView { } 17 + }
+8
packages/emitter/test/integration/atproto/input/chat/bsky/convo/sendMessageBatch.tsp
··· 15 @required message: chat.bsky.convo.defs.MessageInput; 16 } 17 }
··· 15 @required message: chat.bsky.convo.defs.MessageInput; 16 } 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace chat.bsky.convo.defs { 23 + model MessageView { } 24 + model MessageInput { } 25 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/unmuteConvo.tsp
··· 8 @required convo: chat.bsky.convo.defs.ConvoView; 9 }; 10 }
··· 8 @required convo: chat.bsky.convo.defs.ConvoView; 9 }; 10 } 11 + 12 + // --- Externals --- 13 + 14 + @external 15 + namespace chat.bsky.convo.defs { 16 + model ConvoView { } 17 + }
+7
packages/emitter/test/integration/atproto/input/chat/bsky/convo/updateRead.tsp
··· 9 @required convo: chat.bsky.convo.defs.ConvoView; 10 }; 11 }
··· 9 @required convo: chat.bsky.convo.defs.ConvoView; 10 }; 11 } 12 + 13 + // --- Externals --- 14 + 15 + @external 16 + namespace chat.bsky.convo.defs { 17 + model ConvoView { } 18 + }
+8
packages/emitter/test/integration/atproto/input/chat/bsky/moderation/getMessageContext.tsp
··· 20 )[]; 21 }; 22 }
··· 20 )[]; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace chat.bsky.convo.defs { 28 + model MessageView { } 29 + model DeletedMessageView { } 30 + }
+8
packages/emitter/test/integration/atproto/input/com/atproto/admin/defs.tsp
··· 38 @required value: string; 39 } 40 }
··· 38 @required value: string; 39 } 40 } 41 + 42 + // --- Externals --- 43 + 44 + @external 45 + namespace com.atproto.server.defs { 46 + model InviteCode { } 47 + model InviteCodeUse { } 48 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/admin/getAccountInfo.tsp
··· 7 @required did: did 8 ): com.atproto.admin.defs.AccountView; 9 }
··· 7 @required did: did 8 ): com.atproto.admin.defs.AccountView; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace com.atproto.admin.defs { 15 + model AccountView { } 16 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/admin/getAccountInfos.tsp
··· 9 @required infos: com.atproto.admin.defs.AccountView[]; 10 }; 11 }
··· 9 @required infos: com.atproto.admin.defs.AccountView[]; 10 }; 11 } 12 + 13 + // --- Externals --- 14 + 15 + @external 16 + namespace com.atproto.admin.defs { 17 + model AccountView { } 18 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/admin/getInviteCodes.tsp
··· 16 @required codes: com.atproto.server.defs.InviteCode[]; 17 }; 18 }
··· 16 @required codes: com.atproto.server.defs.InviteCode[]; 17 }; 18 } 19 + 20 + // --- Externals --- 21 + 22 + @external 23 + namespace com.atproto.server.defs { 24 + model InviteCode { } 25 + }
+14
packages/emitter/test/integration/atproto/input/com/atproto/admin/getSubjectStatus.tsp
··· 20 deactivated?: com.atproto.admin.defs.StatusAttr; 21 }; 22 }
··· 20 deactivated?: com.atproto.admin.defs.StatusAttr; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace com.atproto.admin.defs { 28 + model RepoRef { } 29 + model RepoBlobRef { } 30 + model StatusAttr { } 31 + } 32 + 33 + @external 34 + namespace com.atproto.repo.strongRef { 35 + model Main { } 36 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/admin/searchAccounts.tsp
··· 15 @required accounts: com.atproto.admin.defs.AccountView[]; 16 }; 17 }
··· 15 @required accounts: com.atproto.admin.defs.AccountView[]; 16 }; 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace com.atproto.admin.defs { 23 + model AccountView { } 24 + }
+14
packages/emitter/test/integration/atproto/input/com/atproto/admin/updateSubjectStatus.tsp
··· 26 takedown?: com.atproto.admin.defs.StatusAttr; 27 }; 28 }
··· 26 takedown?: com.atproto.admin.defs.StatusAttr; 27 }; 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace com.atproto.admin.defs { 34 + model RepoRef { } 35 + model RepoBlobRef { } 36 + model StatusAttr { } 37 + } 38 + 39 + @external 40 + namespace com.atproto.repo.strongRef { 41 + model Main { } 42 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/identity/refreshIdentity.tsp
··· 17 @required identifier: atIdentifier; 18 }): com.atproto.identity.defs.IdentityInfo; 19 }
··· 17 @required identifier: atIdentifier; 18 }): com.atproto.identity.defs.IdentityInfo; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace com.atproto.identity.defs { 25 + model IdentityInfo { } 26 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/identity/resolveIdentity.tsp
··· 19 identifier: atIdentifier 20 ): com.atproto.identity.defs.IdentityInfo; 21 }
··· 19 identifier: atIdentifier 20 ): com.atproto.identity.defs.IdentityInfo; 21 } 22 + 23 + // --- Externals --- 24 + 25 + @external 26 + namespace com.atproto.identity.defs { 27 + model IdentityInfo { } 28 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/label/queryLabels.tsp
··· 21 @required labels: com.atproto.label.defs.Label[]; 22 }; 23 }
··· 21 @required labels: com.atproto.label.defs.Label[]; 22 }; 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace com.atproto.label.defs { 29 + model Label { } 30 + }
+8
packages/emitter/test/integration/atproto/input/com/atproto/label/subscribeLabels.tsp
··· 21 cursor?: integer 22 ): (Labels | Info); 23 }
··· 21 cursor?: integer 22 ): (Labels | Info); 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace com.atproto.label.defs { 29 + model Label { } 30 + model Info { } 31 + }
+17
packages/emitter/test/integration/atproto/input/com/atproto/moderation/createReport.tsp
··· 50 meta?: unknown; 51 } 52 }
··· 50 meta?: unknown; 51 } 52 } 53 + 54 + // --- Externals --- 55 + 56 + @external 57 + namespace com.atproto.moderation.defs { 58 + model ReasonType { } 59 + } 60 + 61 + @external 62 + namespace com.atproto.admin.defs { 63 + model RepoRef { } 64 + } 65 + 66 + @external 67 + namespace com.atproto.repo.strongRef { 68 + model Main { } 69 + }
+51
packages/emitter/test/integration/atproto/input/com/atproto/moderation/defs.tsp
··· 93 string, 94 } 95 }
··· 93 string, 94 } 95 } 96 + 97 + // --- Externals --- 98 + 99 + @external 100 + namespace tools.ozone.report.defs { 101 + @token model ReasonAppeal { } 102 + @token model ReasonChildSafetyCSAM { } 103 + @token model ReasonChildSafetyEndangerment { } 104 + @token model ReasonChildSafetyGroom { } 105 + @token model ReasonChildSafetyHarassment { } 106 + @token model ReasonChildSafetyMinorPrivacy { } 107 + @token model ReasonChildSafetyOther { } 108 + @token model ReasonChildSafetyPromotion { } 109 + @token model ReasonCivicDisclosure { } 110 + @token model ReasonCivicElectoralProcess { } 111 + @token model ReasonCivicImpersonation { } 112 + @token model ReasonCivicInterference { } 113 + @token model ReasonCivicMisinformation { } 114 + @token model ReasonHarassmentDoxxing { } 115 + @token model ReasonHarassmentHateSpeech { } 116 + @token model ReasonHarassmentOther { } 117 + @token model ReasonHarassmentTargeted { } 118 + @token model ReasonHarassmentTroll { } 119 + @token model ReasonMisleadingBot { } 120 + @token model ReasonMisleadingImpersonation { } 121 + @token model ReasonMisleadingMisinformation { } 122 + @token model ReasonMisleadingOther { } 123 + @token model ReasonMisleadingScam { } 124 + @token model ReasonMisleadingSpam { } 125 + @token model ReasonMisleadingSyntheticContent { } 126 + @token model ReasonRuleBanEvasion { } 127 + @token model ReasonRuleOther { } 128 + @token model ReasonRuleProhibitedSales { } 129 + @token model ReasonRuleSiteSecurity { } 130 + @token model ReasonRuleStolenContent { } 131 + @token model ReasonSexualAbuseContent { } 132 + @token model ReasonSexualAnimal { } 133 + @token model ReasonSexualDeepfake { } 134 + @token model ReasonSexualNCII { } 135 + @token model ReasonSexualOther { } 136 + @token model ReasonSexualSextortion { } 137 + @token model ReasonSexualUnlabeled { } 138 + @token model ReasonViolenceAnimalWelfare { } 139 + @token model ReasonViolenceExtremistContent { } 140 + @token model ReasonViolenceGlorification { } 141 + @token model ReasonViolenceGraphicContent { } 142 + @token model ReasonViolenceOther { } 143 + @token model ReasonViolenceSelfHarm { } 144 + @token model ReasonViolenceThreats { } 145 + @token model ReasonViolenceTrafficking { } 146 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/repo/applyWrites.tsp
··· 79 80 model DeleteResult {} 81 }
··· 79 80 model DeleteResult {} 81 } 82 + 83 + // --- Externals --- 84 + 85 + @external 86 + namespace com.atproto.repo.defs { 87 + model CommitMeta { } 88 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/repo/createRecord.tsp
··· 36 /** Indicates that 'swapCommit' didn't match current repo commit. */ 37 model InvalidSwap {} 38 }
··· 36 /** Indicates that 'swapCommit' didn't match current repo commit. */ 37 model InvalidSwap {} 38 } 39 + 40 + // --- Externals --- 41 + 42 + @external 43 + namespace com.atproto.repo.defs { 44 + model CommitMeta { } 45 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/repo/deleteRecord.tsp
··· 28 29 model InvalidSwap {} 30 }
··· 28 29 model InvalidSwap {} 30 } 31 + 32 + // --- Externals --- 33 + 34 + @external 35 + namespace com.atproto.repo.defs { 36 + model CommitMeta { } 37 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/repo/putRecord.tsp
··· 39 40 model InvalidSwap {} 41 }
··· 39 40 model InvalidSwap {} 41 } 42 + 43 + // --- Externals --- 44 + 45 + @external 46 + namespace com.atproto.repo.defs { 47 + model CommitMeta { } 48 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/server/getAccountInviteCodes.tsp
··· 15 @required codes: com.atproto.server.defs.InviteCode[]; 16 }; 17 }
··· 15 @required codes: com.atproto.server.defs.InviteCode[]; 16 }; 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace com.atproto.server.defs { 23 + model InviteCode { } 24 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/sync/getHostStatus.tsp
··· 22 23 model HostNotFound {} 24 }
··· 22 23 model HostNotFound {} 24 } 25 + 26 + // --- Externals --- 27 + 28 + @external 29 + namespace com.atproto.sync.defs { 30 + model HostStatus { } 31 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/sync/listHosts.tsp
··· 29 status?: com.atproto.sync.defs.HostStatus; 30 } 31 }
··· 29 status?: com.atproto.sync.defs.HostStatus; 30 } 31 } 32 + 33 + // --- Externals --- 34 + 35 + @external 36 + namespace com.atproto.sync.defs { 37 + model HostStatus { } 38 + }
+7
packages/emitter/test/integration/atproto/input/com/atproto/temp/fetchLabels.tsp
··· 13 @required labels: com.atproto.label.defs.Label[]; 14 }; 15 }
··· 13 @required labels: com.atproto.label.defs.Label[]; 14 }; 15 } 16 + 17 + // --- Externals --- 18 + 19 + @external 20 + namespace com.atproto.label.defs { 21 + model Label { } 22 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/communication/createTemplate.tsp
··· 26 createdBy?: did; 27 }): tools.ozone.communication.defs.TemplateView; 28 }
··· 26 createdBy?: did; 27 }): tools.ozone.communication.defs.TemplateView; 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace tools.ozone.communication.defs { 34 + model TemplateView { } 35 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/communication/listTemplates.tsp
··· 7 @required communicationTemplates: tools.ozone.communication.defs.TemplateView[]; 8 }; 9 }
··· 7 @required communicationTemplates: tools.ozone.communication.defs.TemplateView[]; 8 }; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace tools.ozone.communication.defs { 15 + model TemplateView { } 16 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/communication/updateTemplate.tsp
··· 29 disabled?: boolean; 30 }): tools.ozone.communication.defs.TemplateView; 31 }
··· 29 disabled?: boolean; 30 }): tools.ozone.communication.defs.TemplateView; 31 } 32 + 33 + // --- Externals --- 34 + 35 + @external 36 + namespace tools.ozone.communication.defs { 37 + model TemplateView { } 38 + }
+34
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/defs.tsp
··· 601 @token 602 model TimelineEventPlcTombstone {} 603 }
··· 601 @token 602 model TimelineEventPlcTombstone {} 603 } 604 + 605 + // --- Externals --- 606 + 607 + @external 608 + namespace com.atproto.admin.defs { 609 + model RepoRef { } 610 + model ThreatSignature { } 611 + } 612 + 613 + @external 614 + namespace com.atproto.repo.strongRef { 615 + model Main { } 616 + } 617 + 618 + @external 619 + namespace chat.bsky.convo.defs { 620 + model MessageRef { } 621 + } 622 + 623 + @external 624 + namespace com.atproto.moderation.defs { 625 + model SubjectType { } 626 + model ReasonType { } 627 + } 628 + 629 + @external 630 + namespace com.atproto.server.defs { 631 + model InviteCode { } 632 + } 633 + 634 + @external 635 + namespace com.atproto.label.defs { 636 + model Label { } 637 + }
+40
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/emitEvent.tsp
··· 54 externalId?: string; 55 }): tools.ozone.moderation.defs.ModEventView; 56 }
··· 54 externalId?: string; 55 }): tools.ozone.moderation.defs.ModEventView; 56 } 57 + 58 + // --- Externals --- 59 + 60 + @external 61 + namespace tools.ozone.moderation.defs { 62 + model ModEventTakedown { } 63 + model ModEventAcknowledge { } 64 + model ModEventEscalate { } 65 + model ModEventComment { } 66 + model ModEventLabel { } 67 + model ModEventReport { } 68 + model ModEventMute { } 69 + model ModEventUnmute { } 70 + model ModEventMuteReporter { } 71 + model ModEventUnmuteReporter { } 72 + model ModEventReverseTakedown { } 73 + model ModEventResolveAppeal { } 74 + model ModEventEmail { } 75 + model ModEventDivert { } 76 + model ModEventTag { } 77 + model AccountEvent { } 78 + model IdentityEvent { } 79 + model RecordEvent { } 80 + model ModEventPriorityScore { } 81 + model AgeAssuranceEvent { } 82 + model AgeAssuranceOverrideEvent { } 83 + model RevokeAccountCredentialsEvent { } 84 + model ModTool { } 85 + model ModEventView { } 86 + } 87 + 88 + @external 89 + namespace com.atproto.admin.defs { 90 + model RepoRef { } 91 + } 92 + 93 + @external 94 + namespace com.atproto.repo.strongRef { 95 + model Main { } 96 + }
+39
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getAccountTimeline.tsp
··· 55 @required count: integer; 56 } 57 }
··· 55 @required count: integer; 56 } 57 } 58 + 59 + // --- Externals --- 60 + 61 + @external 62 + namespace tools.ozone.moderation.defs { 63 + model ModEventTakedown { } 64 + model ModEventReverseTakedown { } 65 + model ModEventComment { } 66 + model ModEventReport { } 67 + model ModEventLabel { } 68 + model ModEventAcknowledge { } 69 + model ModEventEscalate { } 70 + model ModEventMute { } 71 + model ModEventUnmute { } 72 + model ModEventMuteReporter { } 73 + model ModEventUnmuteReporter { } 74 + model ModEventEmail { } 75 + model ModEventResolveAppeal { } 76 + model ModEventDivert { } 77 + model ModEventTag { } 78 + model AccountEvent { } 79 + model IdentityEvent { } 80 + model RecordEvent { } 81 + model ModEventPriorityScore { } 82 + model RevokeAccountCredentialsEvent { } 83 + model AgeAssuranceEvent { } 84 + model AgeAssuranceOverrideEvent { } 85 + @token model TimelineEventPlcCreate { } 86 + @token model TimelineEventPlcOperation { } 87 + @token model TimelineEventPlcTombstone { } 88 + } 89 + 90 + @external 91 + namespace tools.ozone.hosting.getAccountHistory { 92 + model AccountCreated { } 93 + model EmailConfirmed { } 94 + model PasswordUpdated { } 95 + model HandleUpdated { } 96 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getEvent.tsp
··· 5 @query 6 op main(@required id: integer): tools.ozone.moderation.defs.ModEventViewDetail; 7 }
··· 5 @query 6 op main(@required id: integer): tools.ozone.moderation.defs.ModEventViewDetail; 7 } 8 + 9 + // --- Externals --- 10 + 11 + @external 12 + namespace tools.ozone.moderation.defs { 13 + model ModEventViewDetail { } 14 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getRecord.tsp
··· 8 @errors(RecordNotFound) 9 op main(@required uri: atUri, cid?: cid): tools.ozone.moderation.defs.RecordViewDetail; 10 }
··· 8 @errors(RecordNotFound) 9 op main(@required uri: atUri, cid?: cid): tools.ozone.moderation.defs.RecordViewDetail; 10 } 11 + 12 + // --- Externals --- 13 + 14 + @external 15 + namespace tools.ozone.moderation.defs { 16 + model RecordViewDetail { } 17 + }
+8
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getRecords.tsp
··· 16 )[]; 17 }; 18 }
··· 16 )[]; 17 }; 18 } 19 + 20 + // --- Externals --- 21 + 22 + @external 23 + namespace tools.ozone.moderation.defs { 24 + model RecordViewDetail { } 25 + model RecordViewNotFound { } 26 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getRepo.tsp
··· 8 @errors(RepoNotFound) 9 op main(@required did: did): tools.ozone.moderation.defs.RepoViewDetail; 10 }
··· 8 @errors(RepoNotFound) 9 op main(@required did: did): tools.ozone.moderation.defs.RepoViewDetail; 10 } 11 + 12 + // --- Externals --- 13 + 14 + @external 15 + namespace tools.ozone.moderation.defs { 16 + model RepoViewDetail { } 17 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getReporterStats.tsp
··· 11 @required stats: tools.ozone.moderation.defs.ReporterStats[]; 12 }; 13 }
··· 11 @required stats: tools.ozone.moderation.defs.ReporterStats[]; 12 }; 13 } 14 + 15 + // --- Externals --- 16 + 17 + @external 18 + namespace tools.ozone.moderation.defs { 19 + model ReporterStats { } 20 + }
+8
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getRepos.tsp
··· 16 )[]; 17 }; 18 }
··· 16 )[]; 17 }; 18 } 19 + 20 + // --- Externals --- 21 + 22 + @external 23 + namespace tools.ozone.moderation.defs { 24 + model RepoViewDetail { } 25 + model RepoViewNotFound { } 26 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/getSubjects.tsp
··· 12 @required subjects: tools.ozone.moderation.defs.SubjectView[]; 13 }; 14 }
··· 12 @required subjects: tools.ozone.moderation.defs.SubjectView[]; 13 }; 14 } 15 + 16 + // --- Externals --- 17 + 18 + @external 19 + namespace tools.ozone.moderation.defs { 20 + model SubjectView { } 21 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/queryEvents.tsp
··· 80 @required events: tools.ozone.moderation.defs.ModEventView[]; 81 }; 82 }
··· 80 @required events: tools.ozone.moderation.defs.ModEventView[]; 81 }; 82 } 83 + 84 + // --- Externals --- 85 + 86 + @external 87 + namespace tools.ozone.moderation.defs { 88 + model ModEventView { } 89 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/queryStatuses.tsp
··· 131 @required subjectStatuses: tools.ozone.moderation.defs.SubjectStatusView[]; 132 }; 133 }
··· 131 @required subjectStatuses: tools.ozone.moderation.defs.SubjectStatusView[]; 132 }; 133 } 134 + 135 + // --- Externals --- 136 + 137 + @external 138 + namespace tools.ozone.moderation.defs { 139 + model SubjectStatusView { } 140 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/moderation/searchRepos.tsp
··· 20 @required repos: tools.ozone.moderation.defs.RepoView[]; 21 }; 22 }
··· 20 @required repos: tools.ozone.moderation.defs.RepoView[]; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace tools.ozone.moderation.defs { 28 + model RepoView { } 29 + }
+10
packages/emitter/test/integration/atproto/input/tools/ozone/safelink/addRule.tsp
··· 28 createdBy?: did; 29 }): tools.ozone.safelink.defs.Event; 30 }
··· 28 createdBy?: did; 29 }): tools.ozone.safelink.defs.Event; 30 } 31 + 32 + // --- Externals --- 33 + 34 + @external 35 + namespace tools.ozone.safelink.defs { 36 + model PatternType { } 37 + model ActionType { } 38 + model ReasonType { } 39 + model Event { } 40 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/safelink/queryEvents.tsp
··· 27 @required events: tools.ozone.safelink.defs.Event[]; 28 }; 29 }
··· 27 @required events: tools.ozone.safelink.defs.Event[]; 28 }; 29 } 30 + 31 + // --- Externals --- 32 + 33 + @external 34 + namespace tools.ozone.safelink.defs { 35 + model Event { } 36 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/safelink/queryRules.tsp
··· 36 @required rules: tools.ozone.safelink.defs.UrlRule[]; 37 }; 38 }
··· 36 @required rules: tools.ozone.safelink.defs.UrlRule[]; 37 }; 38 } 39 + 40 + // --- Externals --- 41 + 42 + @external 43 + namespace tools.ozone.safelink.defs { 44 + model UrlRule { } 45 + }
+8
packages/emitter/test/integration/atproto/input/tools/ozone/safelink/removeRule.tsp
··· 21 createdBy?: did; 22 }): tools.ozone.safelink.defs.Event; 23 }
··· 21 createdBy?: did; 22 }): tools.ozone.safelink.defs.Event; 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace tools.ozone.safelink.defs { 29 + model PatternType { } 30 + model Event { } 31 + }
+10
packages/emitter/test/integration/atproto/input/tools/ozone/safelink/updateRule.tsp
··· 25 createdBy?: did; 26 }): tools.ozone.safelink.defs.Event; 27 }
··· 25 createdBy?: did; 26 }): tools.ozone.safelink.defs.Event; 27 } 28 + 29 + // --- Externals --- 30 + 31 + @external 32 + namespace tools.ozone.safelink.defs { 33 + model PatternType { } 34 + model ActionType { } 35 + model ReasonType { } 36 + model Event { } 37 + }
+16 -7
packages/emitter/test/integration/atproto/input/tools/ozone/server/getConfig.tsp
··· 19 } 20 21 model ViewerConfig { 22 - role?: ( 23 - | "tools.ozone.team.defs#roleAdmin" 24 - | "tools.ozone.team.defs#roleModerator" 25 - | "tools.ozone.team.defs#roleTriage" 26 - | "tools.ozone.team.defs#roleVerifier" 27 - | string 28 - ); 29 } 30 }
··· 19 } 20 21 model ViewerConfig { 22 + role?: 23 + | tools.ozone.team.defs.RoleAdmin 24 + | tools.ozone.team.defs.RoleModerator 25 + | tools.ozone.team.defs.RoleTriage 26 + | tools.ozone.team.defs.RoleVerifier 27 + | string; 28 } 29 } 30 + 31 + // --- Externals --- 32 + 33 + @external 34 + namespace tools.ozone.team.defs { 35 + @token model RoleAdmin { } 36 + @token model RoleModerator { } 37 + @token model RoleTriage { } 38 + @token model RoleVerifier { } 39 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/set/getValues.tsp
··· 23 cursor?: string; 24 }; 25 }
··· 23 cursor?: string; 24 }; 25 } 26 + 27 + // --- Externals --- 28 + 29 + @external 30 + namespace tools.ozone.set.defs { 31 + model SetView { } 32 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/set/querySets.tsp
··· 37 cursor?: string; 38 }; 39 }
··· 37 cursor?: string; 38 }; 39 } 40 + 41 + // --- Externals --- 42 + 43 + @external 44 + namespace tools.ozone.set.defs { 45 + model SetView { } 46 + }
+8
packages/emitter/test/integration/atproto/input/tools/ozone/set/upsertSet.tsp
··· 5 @procedure 6 op main(input: tools.ozone.set.defs.Set): tools.ozone.set.defs.SetView; 7 }
··· 5 @procedure 6 op main(input: tools.ozone.set.defs.Set): tools.ozone.set.defs.SetView; 7 } 8 + 9 + // --- Externals --- 10 + 11 + @external 12 + namespace tools.ozone.set.defs { 13 + model Set { } 14 + model SetView { } 15 + }
+14 -4
packages/emitter/test/integration/atproto/input/tools/ozone/setting/defs.tsp
··· 16 updatedAt?: datetime; 17 18 managerRole?: 19 - | "tools.ozone.team.defs#roleModerator" 20 - | "tools.ozone.team.defs#roleTriage" 21 - | "tools.ozone.team.defs#roleAdmin" 22 - | "tools.ozone.team.defs#roleVerifier" 23 | string; 24 25 @required scope: "instance" | "personal" | string; ··· 27 @required lastUpdatedBy: did; 28 } 29 }
··· 16 updatedAt?: datetime; 17 18 managerRole?: 19 + | tools.ozone.team.defs.RoleModerator 20 + | tools.ozone.team.defs.RoleTriage 21 + | tools.ozone.team.defs.RoleAdmin 22 + | tools.ozone.team.defs.RoleVerifier 23 | string; 24 25 @required scope: "instance" | "personal" | string; ··· 27 @required lastUpdatedBy: did; 28 } 29 } 30 + 31 + // --- Externals --- 32 + 33 + @external 34 + namespace tools.ozone.team.defs { 35 + @token model RoleModerator { } 36 + @token model RoleTriage { } 37 + @token model RoleAdmin { } 38 + @token model RoleVerifier { } 39 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/setting/listOptions.tsp
··· 24 @required options: tools.ozone.setting.defs.Option[]; 25 }; 26 }
··· 24 @required options: tools.ozone.setting.defs.Option[]; 25 }; 26 } 27 + 28 + // --- Externals --- 29 + 30 + @external 31 + namespace tools.ozone.setting.defs { 32 + model Option { } 33 + }
+19 -4
packages/emitter/test/integration/atproto/input/tools/ozone/setting/upsertOption.tsp
··· 14 description?: string; 15 16 managerRole?: 17 - | "tools.ozone.team.defs#roleModerator" 18 - | "tools.ozone.team.defs#roleTriage" 19 - | "tools.ozone.team.defs#roleVerifier" 20 - | "tools.ozone.team.defs#roleAdmin" 21 | string; 22 }): { 23 @required option: tools.ozone.setting.defs.Option; 24 }; 25 }
··· 14 description?: string; 15 16 managerRole?: 17 + | tools.ozone.team.defs.RoleModerator 18 + | tools.ozone.team.defs.RoleTriage 19 + | tools.ozone.team.defs.RoleVerifier 20 + | tools.ozone.team.defs.RoleAdmin 21 | string; 22 }): { 23 @required option: tools.ozone.setting.defs.Option; 24 }; 25 } 26 + 27 + // --- Externals --- 28 + 29 + @external 30 + namespace tools.ozone.team.defs { 31 + @token model RoleModerator { } 32 + @token model RoleTriage { } 33 + @token model RoleVerifier { } 34 + @token model RoleAdmin { } 35 + } 36 + 37 + @external 38 + namespace tools.ozone.setting.defs { 39 + model Option { } 40 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/signature/findCorrelation.tsp
··· 7 @required details: tools.ozone.signature.defs.SigDetail[]; 8 }; 9 }
··· 7 @required details: tools.ozone.signature.defs.SigDetail[]; 8 }; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace tools.ozone.signature.defs { 15 + model SigDetail { } 16 + }
+12
packages/emitter/test/integration/atproto/input/tools/ozone/signature/findRelatedAccounts.tsp
··· 23 similarities?: tools.ozone.signature.defs.SigDetail[]; 24 } 25 }
··· 23 similarities?: tools.ozone.signature.defs.SigDetail[]; 24 } 25 } 26 + 27 + // --- Externals --- 28 + 29 + @external 30 + namespace com.atproto.admin.defs { 31 + model AccountView { } 32 + } 33 + 34 + @external 35 + namespace tools.ozone.signature.defs { 36 + model SigDetail { } 37 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/signature/searchAccounts.tsp
··· 17 @required accounts: com.atproto.admin.defs.AccountView[]; 18 }; 19 }
··· 17 @required accounts: com.atproto.admin.defs.AccountView[]; 18 }; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace com.atproto.admin.defs { 25 + model AccountView { } 26 + }
+17 -7
packages/emitter/test/integration/atproto/input/tools/ozone/team/addMember.tsp
··· 11 @required did: did; 12 13 @required 14 - role: ( 15 - | "tools.ozone.team.defs#roleAdmin" 16 - | "tools.ozone.team.defs#roleModerator" 17 - | "tools.ozone.team.defs#roleVerifier" 18 - | "tools.ozone.team.defs#roleTriage" 19 - | string 20 - ); 21 }): tools.ozone.team.defs.Member; 22 }
··· 11 @required did: did; 12 13 @required 14 + role: 15 + | tools.ozone.team.defs.RoleAdmin 16 + | tools.ozone.team.defs.RoleModerator 17 + | tools.ozone.team.defs.RoleVerifier 18 + | tools.ozone.team.defs.RoleTriage 19 + | string; 20 }): tools.ozone.team.defs.Member; 21 } 22 + 23 + // --- Externals --- 24 + 25 + @external 26 + namespace tools.ozone.team.defs { 27 + @token model RoleAdmin { } 28 + @token model RoleModerator { } 29 + @token model RoleVerifier { } 30 + @token model RoleTriage { } 31 + model Member { } 32 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/team/defs.tsp
··· 27 @token 28 model RoleVerifier {} 29 }
··· 27 @token 28 model RoleVerifier {} 29 } 30 + 31 + // --- Externals --- 32 + 33 + @external 34 + namespace app.bsky.actor.defs { 35 + model ProfileViewDetailed { } 36 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/team/listMembers.tsp
··· 18 @required members: tools.ozone.team.defs.Member[]; 19 }; 20 }
··· 18 @required members: tools.ozone.team.defs.Member[]; 19 }; 20 } 21 + 22 + // --- Externals --- 23 + 24 + @external 25 + namespace tools.ozone.team.defs { 26 + model Member { } 27 + }
+17 -7
packages/emitter/test/integration/atproto/input/tools/ozone/team/updateMember.tsp
··· 11 @required did: did; 12 disabled?: boolean; 13 14 - role?: ( 15 - | "tools.ozone.team.defs#roleAdmin" 16 - | "tools.ozone.team.defs#roleModerator" 17 - | "tools.ozone.team.defs#roleVerifier" 18 - | "tools.ozone.team.defs#roleTriage" 19 - | string 20 - ); 21 }): tools.ozone.team.defs.Member; 22 }
··· 11 @required did: did; 12 disabled?: boolean; 13 14 + role?: 15 + | tools.ozone.team.defs.RoleAdmin 16 + | tools.ozone.team.defs.RoleModerator 17 + | tools.ozone.team.defs.RoleVerifier 18 + | tools.ozone.team.defs.RoleTriage 19 + | string; 20 }): tools.ozone.team.defs.Member; 21 } 22 + 23 + // --- Externals --- 24 + 25 + @external 26 + namespace tools.ozone.team.defs { 27 + @token model RoleAdmin { } 28 + @token model RoleModerator { } 29 + @token model RoleVerifier { } 30 + @token model RoleTriage { } 31 + model Member { } 32 + }
+8
packages/emitter/test/integration/atproto/input/tools/ozone/verification/defs.tsp
··· 52 ); 53 } 54 }
··· 52 ); 53 } 54 } 55 + 56 + // --- Externals --- 57 + 58 + @external 59 + namespace tools.ozone.moderation.defs { 60 + model RepoViewDetail { } 61 + model RepoViewNotFound { } 62 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/verification/grantVerifications.tsp
··· 41 subject: did; 42 } 43 }
··· 41 subject: did; 42 } 43 } 44 + 45 + // --- Externals --- 46 + 47 + @external 48 + namespace tools.ozone.verification.defs { 49 + model VerificationView { } 50 + }
+7
packages/emitter/test/integration/atproto/input/tools/ozone/verification/listVerifications.tsp
··· 43 @required verifications: tools.ozone.verification.defs.VerificationView[]; 44 }; 45 }
··· 43 @required verifications: tools.ozone.verification.defs.VerificationView[]; 44 }; 45 } 46 + 47 + // --- Externals --- 48 + 49 + @external 50 + namespace tools.ozone.verification.defs { 51 + model VerificationView { } 52 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/admin/defs.tsp
··· 38 @required value: string; 39 } 40 }
··· 38 @required value: string; 39 } 40 } 41 + 42 + // --- Externals --- 43 + 44 + @external 45 + namespace com.atproto.server.defs { 46 + model InviteCode { } 47 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/admin/getAccountInfo.tsp
··· 7 @required did: did 8 ): com.atproto.admin.defs.AccountView; 9 }
··· 7 @required did: did 8 ): com.atproto.admin.defs.AccountView; 9 } 10 + 11 + // --- Externals --- 12 + 13 + @external 14 + namespace com.atproto.admin.defs { 15 + model AccountView { } 16 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/admin/getAccountInfos.tsp
··· 9 @required infos: com.atproto.admin.defs.AccountView[]; 10 }; 11 }
··· 9 @required infos: com.atproto.admin.defs.AccountView[]; 10 }; 11 } 12 + 13 + // --- Externals --- 14 + 15 + @external 16 + namespace com.atproto.admin.defs { 17 + model AccountView { } 18 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/admin/getInviteCodes.tsp
··· 16 @required codes: com.atproto.server.defs.InviteCode[]; 17 }; 18 }
··· 16 @required codes: com.atproto.server.defs.InviteCode[]; 17 }; 18 } 19 + 20 + // --- Externals --- 21 + 22 + @external 23 + namespace com.atproto.server.defs { 24 + model InviteCode { } 25 + }
+14
packages/emitter/test/integration/lexicon-examples/input/com/atproto/admin/getSubjectStatus.tsp
··· 20 deactivated?: com.atproto.admin.defs.StatusAttr; 21 }; 22 }
··· 20 deactivated?: com.atproto.admin.defs.StatusAttr; 21 }; 22 } 23 + 24 + // --- Externals --- 25 + 26 + @external 27 + namespace com.atproto.admin.defs { 28 + model RepoRef { } 29 + model RepoBlobRef { } 30 + model StatusAttr { } 31 + } 32 + 33 + @external 34 + namespace com.atproto.repo.strongRef { 35 + model Main { } 36 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/admin/searchAccounts.tsp
··· 15 @required accounts: com.atproto.admin.defs.AccountView[]; 16 }; 17 }
··· 15 @required accounts: com.atproto.admin.defs.AccountView[]; 16 }; 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace com.atproto.admin.defs { 23 + model AccountView { } 24 + }
+14
packages/emitter/test/integration/lexicon-examples/input/com/atproto/admin/updateSubjectStatus.tsp
··· 26 takedown?: com.atproto.admin.defs.StatusAttr; 27 }; 28 }
··· 26 takedown?: com.atproto.admin.defs.StatusAttr; 27 }; 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace com.atproto.admin.defs { 34 + model RepoRef { } 35 + model RepoBlobRef { } 36 + model StatusAttr { } 37 + } 38 + 39 + @external 40 + namespace com.atproto.repo.strongRef { 41 + model Main { } 42 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/identity/refreshIdentity.tsp
··· 17 @required identifier: atIdentifier; 18 }): com.atproto.identity.defs.IdentityInfo; 19 }
··· 17 @required identifier: atIdentifier; 18 }): com.atproto.identity.defs.IdentityInfo; 19 } 20 + 21 + // --- Externals --- 22 + 23 + @external 24 + namespace com.atproto.identity.defs { 25 + model IdentityInfo { } 26 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/identity/resolveIdentity.tsp
··· 19 identifier: atIdentifier 20 ): com.atproto.identity.defs.IdentityInfo; 21 }
··· 19 identifier: atIdentifier 20 ): com.atproto.identity.defs.IdentityInfo; 21 } 22 + 23 + // --- Externals --- 24 + 25 + @external 26 + namespace com.atproto.identity.defs { 27 + model IdentityInfo { } 28 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/label/queryLabels.tsp
··· 21 @required labels: com.atproto.label.defs.Label[]; 22 }; 23 }
··· 21 @required labels: com.atproto.label.defs.Label[]; 22 }; 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace com.atproto.label.defs { 29 + model Label { } 30 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/label/subscribeLabels.tsp
··· 21 cursor?: integer 22 ): (Labels | Info); 23 }
··· 21 cursor?: integer 22 ): (Labels | Info); 23 } 24 + 25 + // --- Externals --- 26 + 27 + @external 28 + namespace com.atproto.label.defs { 29 + model Label { } 30 + }
+17
packages/emitter/test/integration/lexicon-examples/input/com/atproto/moderation/createReport.tsp
··· 50 meta?: unknown; 51 } 52 }
··· 50 meta?: unknown; 51 } 52 } 53 + 54 + // --- Externals --- 55 + 56 + @external 57 + namespace com.atproto.moderation.defs { 58 + model ReasonType { } 59 + } 60 + 61 + @external 62 + namespace com.atproto.admin.defs { 63 + model RepoRef { } 64 + } 65 + 66 + @external 67 + namespace com.atproto.repo.strongRef { 68 + model Main { } 69 + }
+51
packages/emitter/test/integration/lexicon-examples/input/com/atproto/moderation/defs.tsp
··· 102 string, 103 } 104 }
··· 102 string, 103 } 104 } 105 + 106 + // --- Externals --- 107 + 108 + @external 109 + namespace tools.ozone.report.defs { 110 + @token model ReasonAppeal { } 111 + @token model ReasonViolenceAnimalWelfare { } 112 + @token model ReasonViolenceThreats { } 113 + @token model ReasonViolenceGraphicContent { } 114 + @token model ReasonViolenceSelfHarm { } 115 + @token model ReasonViolenceGlorification { } 116 + @token model ReasonViolenceExtremistContent { } 117 + @token model ReasonViolenceTrafficking { } 118 + @token model ReasonViolenceOther { } 119 + @token model ReasonSexualAbuseContent { } 120 + @token model ReasonSexualNCII { } 121 + @token model ReasonSexualSextortion { } 122 + @token model ReasonSexualDeepfake { } 123 + @token model ReasonSexualAnimal { } 124 + @token model ReasonSexualUnlabeled { } 125 + @token model ReasonSexualOther { } 126 + @token model ReasonChildSafetyCSAM { } 127 + @token model ReasonChildSafetyGroom { } 128 + @token model ReasonChildSafetyMinorPrivacy { } 129 + @token model ReasonChildSafetyEndangerment { } 130 + @token model ReasonChildSafetyHarassment { } 131 + @token model ReasonChildSafetyPromotion { } 132 + @token model ReasonChildSafetyOther { } 133 + @token model ReasonHarassmentTroll { } 134 + @token model ReasonHarassmentTargeted { } 135 + @token model ReasonHarassmentHateSpeech { } 136 + @token model ReasonHarassmentDoxxing { } 137 + @token model ReasonHarassmentOther { } 138 + @token model ReasonMisleadingBot { } 139 + @token model ReasonMisleadingImpersonation { } 140 + @token model ReasonMisleadingSpam { } 141 + @token model ReasonMisleadingScam { } 142 + @token model ReasonMisleadingSyntheticContent { } 143 + @token model ReasonMisleadingMisinformation { } 144 + @token model ReasonMisleadingOther { } 145 + @token model ReasonRuleSiteSecurity { } 146 + @token model ReasonRuleStolenContent { } 147 + @token model ReasonRuleProhibitedSales { } 148 + @token model ReasonRuleBanEvasion { } 149 + @token model ReasonRuleOther { } 150 + @token model ReasonCivicElectoralProcess { } 151 + @token model ReasonCivicDisclosure { } 152 + @token model ReasonCivicInterference { } 153 + @token model ReasonCivicMisinformation { } 154 + @token model ReasonCivicImpersonation { } 155 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/repo/applyWrites.tsp
··· 79 80 model DeleteResult {} 81 }
··· 79 80 model DeleteResult {} 81 } 82 + 83 + // --- Externals --- 84 + 85 + @external 86 + namespace com.atproto.repo.defs { 87 + model CommitMeta { } 88 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/repo/createRecord.tsp
··· 36 /** Indicates that 'swapCommit' didn't match current repo commit. */ 37 model InvalidSwap {} 38 }
··· 36 /** Indicates that 'swapCommit' didn't match current repo commit. */ 37 model InvalidSwap {} 38 } 39 + 40 + // --- Externals --- 41 + 42 + @external 43 + namespace com.atproto.repo.defs { 44 + model CommitMeta { } 45 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/repo/deleteRecord.tsp
··· 28 29 model InvalidSwap {} 30 }
··· 28 29 model InvalidSwap {} 30 } 31 + 32 + // --- Externals --- 33 + 34 + @external 35 + namespace com.atproto.repo.defs { 36 + model CommitMeta { } 37 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/repo/putRecord.tsp
··· 39 40 model InvalidSwap {} 41 }
··· 39 40 model InvalidSwap {} 41 } 42 + 43 + // --- Externals --- 44 + 45 + @external 46 + namespace com.atproto.repo.defs { 47 + model CommitMeta { } 48 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/server/getAccountInviteCodes.tsp
··· 15 @required codes: com.atproto.server.defs.InviteCode[]; 16 }; 17 }
··· 15 @required codes: com.atproto.server.defs.InviteCode[]; 16 }; 17 } 18 + 19 + // --- Externals --- 20 + 21 + @external 22 + namespace com.atproto.server.defs { 23 + model InviteCode { } 24 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/sync/getHostStatus.tsp
··· 22 23 model HostNotFound {} 24 }
··· 22 23 model HostNotFound {} 24 } 25 + 26 + // --- Externals --- 27 + 28 + @external 29 + namespace com.atproto.sync.defs { 30 + model HostStatus { } 31 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/sync/listHosts.tsp
··· 29 status?: com.atproto.sync.defs.HostStatus; 30 } 31 }
··· 29 status?: com.atproto.sync.defs.HostStatus; 30 } 31 } 32 + 33 + // --- Externals --- 34 + 35 + @external 36 + namespace com.atproto.sync.defs { 37 + model HostStatus { } 38 + }
+7
packages/emitter/test/integration/lexicon-examples/input/com/atproto/temp/fetchLabels.tsp
··· 13 @required labels: com.atproto.label.defs.Label[]; 14 }; 15 }
··· 13 @required labels: com.atproto.label.defs.Label[]; 14 }; 15 } 16 + 17 + // --- Externals --- 18 + 19 + @external 20 + namespace com.atproto.label.defs { 21 + model Label { } 22 + }
+7
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/blocks/blockquote.tsp
··· 6 facets?: `pub`.leaflet.richtext.facet.Main[]; 7 } 8 }
··· 6 facets?: `pub`.leaflet.richtext.facet.Main[]; 7 } 8 } 9 + 10 + // --- Externals --- 11 + 12 + @external 13 + namespace `pub`.leaflet.richtext.facet { 14 + model Main { } 15 + }
+7
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/blocks/bskyPost.tsp
··· 5 @required postRef: com.atproto.repo.strongRef.Main; 6 } 7 }
··· 5 @required postRef: com.atproto.repo.strongRef.Main; 6 } 7 } 8 + 9 + // --- Externals --- 10 + 11 + @external 12 + namespace com.atproto.repo.strongRef { 13 + model Main { } 14 + }
+7
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/blocks/header.tsp
··· 10 facets?: `pub`.leaflet.richtext.facet.Main[]; 11 } 12 }
··· 10 facets?: `pub`.leaflet.richtext.facet.Main[]; 11 } 12 } 13 + 14 + // --- Externals --- 15 + 16 + @external 17 + namespace `pub`.leaflet.richtext.facet { 18 + model Main { } 19 + }
+7
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/blocks/text.tsp
··· 6 facets?: `pub`.leaflet.richtext.facet.Main[]; 7 } 8 }
··· 6 facets?: `pub`.leaflet.richtext.facet.Main[]; 7 } 8 } 9 + 10 + // --- Externals --- 11 + 12 + @external 13 + namespace `pub`.leaflet.richtext.facet { 14 + model Main { } 15 + }
+17
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/blocks/unorderedList.tsp
··· 10 children?: ListItem[]; 11 } 12 }
··· 10 children?: ListItem[]; 11 } 12 } 13 + 14 + // --- Externals --- 15 + 16 + @external 17 + namespace `pub`.leaflet.blocks.text { 18 + model Main { } 19 + } 20 + 21 + @external 22 + namespace `pub`.leaflet.blocks.header { 23 + model Main { } 24 + } 25 + 26 + @external 27 + namespace `pub`.leaflet.blocks.image { 28 + model Main { } 29 + }
+12
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/comment.tsp
··· 26 @required parent: atUri; 27 } 28 }
··· 26 @required parent: atUri; 27 } 28 } 29 + 30 + // --- Externals --- 31 + 32 + @external 33 + namespace `pub`.leaflet.richtext.facet { 34 + model Main { } 35 + } 36 + 37 + @external 38 + namespace `pub`.leaflet.pages.linearDocument { 39 + model Quote { } 40 + }
+12
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/document.tsp
··· 23 @required pages: (`pub`.leaflet.pages.linearDocument.Main | unknown)[]; 24 } 25 }
··· 23 @required pages: (`pub`.leaflet.pages.linearDocument.Main | unknown)[]; 24 } 25 } 26 + 27 + // --- Externals --- 28 + 29 + @external 30 + namespace com.atproto.repo.strongRef { 31 + model Main { } 32 + } 33 + 34 + @external 35 + namespace `pub`.leaflet.pages.linearDocument { 36 + model Main { } 37 + }
+57
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/pages/linearDocument.tsp
··· 43 @required offset: integer; 44 } 45 }
··· 43 @required offset: integer; 44 } 45 } 46 + 47 + // --- Externals --- 48 + 49 + @external 50 + namespace `pub`.leaflet.blocks.iframe { 51 + model Main { } 52 + } 53 + 54 + @external 55 + namespace `pub`.leaflet.blocks.text { 56 + model Main { } 57 + } 58 + 59 + @external 60 + namespace `pub`.leaflet.blocks.blockquote { 61 + model Main { } 62 + } 63 + 64 + @external 65 + namespace `pub`.leaflet.blocks.header { 66 + model Main { } 67 + } 68 + 69 + @external 70 + namespace `pub`.leaflet.blocks.image { 71 + model Main { } 72 + } 73 + 74 + @external 75 + namespace `pub`.leaflet.blocks.unorderedList { 76 + model Main { } 77 + } 78 + 79 + @external 80 + namespace `pub`.leaflet.blocks.website { 81 + model Main { } 82 + } 83 + 84 + @external 85 + namespace `pub`.leaflet.blocks.math { 86 + model Main { } 87 + } 88 + 89 + @external 90 + namespace `pub`.leaflet.blocks.code { 91 + model Main { } 92 + } 93 + 94 + @external 95 + namespace `pub`.leaflet.blocks.horizontalRule { 96 + model Main { } 97 + } 98 + 99 + @external 100 + namespace `pub`.leaflet.blocks.bskyPost { 101 + model Main { } 102 + }
+13
packages/emitter/test/integration/lexicon-examples/input/pub/leaflet/publication.tsp
··· 34 accentText?: (`pub`.leaflet.theme.color.Rgba | `pub`.leaflet.theme.color.Rgb | unknown); 35 } 36 }
··· 34 accentText?: (`pub`.leaflet.theme.color.Rgba | `pub`.leaflet.theme.color.Rgb | unknown); 35 } 36 } 37 + 38 + // --- Externals --- 39 + 40 + @external 41 + namespace `pub`.leaflet.theme.color { 42 + model Rgba { } 43 + model Rgb { } 44 + } 45 + 46 + @external 47 + namespace `pub`.leaflet.theme.backgroundImage { 48 + model Main { } 49 + }
+12
packages/emitter/test/integration/lexicon-examples/input/sh/tangled/repo/issue/state.tsp
··· 10 state: "sh.tangled.repo.issue.state.open" | "sh.tangled.repo.issue.state.closed" | string = "sh.tangled.repo.issue.state.open"; 11 } 12 }
··· 10 state: "sh.tangled.repo.issue.state.open" | "sh.tangled.repo.issue.state.closed" | string = "sh.tangled.repo.issue.state.open"; 11 } 12 } 13 + 14 + // --- Externals --- 15 + 16 + @external 17 + namespace sh.tangled.repo.issue.state.open { 18 + @token model Main { } 19 + } 20 + 21 + @external 22 + namespace sh.tangled.repo.issue.state.closed { 23 + @token model Main { } 24 + }
+17
packages/emitter/test/integration/lexicon-examples/input/sh/tangled/repo/pull/status.tsp
··· 10 status: "sh.tangled.repo.pull.status.open" | "sh.tangled.repo.pull.status.closed" | "sh.tangled.repo.pull.status.merged" | string = "sh.tangled.repo.pull.status.open"; 11 } 12 }
··· 10 status: "sh.tangled.repo.pull.status.open" | "sh.tangled.repo.pull.status.closed" | "sh.tangled.repo.pull.status.merged" | string = "sh.tangled.repo.pull.status.open"; 11 } 12 } 13 + 14 + // --- Externals --- 15 + 16 + @external 17 + namespace sh.tangled.repo.pull.status.open { 18 + @token model Main { } 19 + } 20 + 21 + @external 22 + namespace sh.tangled.repo.pull.status.closed { 23 + @token model Main { } 24 + } 25 + 26 + @external 27 + namespace sh.tangled.repo.pull.status.merged { 28 + @token model Main { } 29 + }
+12 -20
packages/emitter/test/integration.test.ts
··· 54 path.join(scenario, "output"), 55 ); 56 57 - // Compile all inputs together (for cross-references) 58 - const tspFiles = Object.keys(inputFiles).filter((f) => 59 - f.endsWith(".tsp"), 60 - ); 61 - let emitResult: EmitResult; 62 - 63 - if (tspFiles.length > 0) { 64 - // Create a virtual main.tsp that imports all other files 65 - const mainContent = 66 - 'import "@typelex/emitter";\n' + 67 - tspFiles.map((f) => `import "./${f}";`).join("\n"); 68 - const filesWithMain = { ...inputFiles, "main.tsp": mainContent }; 69 - emitResult = await doEmit(filesWithMain, "main.tsp"); 70 - } else { 71 - emitResult = { files: {}, diagnostics: [], inputFiles: {} }; 72 - } 73 - 74 // Generate a test for each expected output 75 for (const expectedPath of Object.keys(expectedFiles)) { 76 if (!expectedPath.endsWith(".json")) continue; ··· 80 const hasInput = Object.keys(inputFiles).includes(inputPath); 81 82 if (hasInput) { 83 - it(`should emit ${expectedPath}`, function () { 84 // Check for compilation errors 85 if (emitResult.diagnostics.length > 0) { 86 const formattedDiagnostics = emitResult.diagnostics.map((diag) => ··· 91 ); 92 } 93 94 assert.ok( 95 Object.prototype.hasOwnProperty.call( 96 emitResult.files, 97 - expectedPath, 98 ), 99 `Expected file ${expectedPath} was not produced`, 100 ); 101 102 - const actual = JSON.parse(emitResult.files[expectedPath]); 103 const expected = JSON.parse(expectedFiles[expectedPath]); 104 assert.deepStrictEqual(actual, expected); 105 }); ··· 180 await walk(dir, ""); 181 return result; 182 }
··· 54 path.join(scenario, "output"), 55 ); 56 57 // Generate a test for each expected output 58 for (const expectedPath of Object.keys(expectedFiles)) { 59 if (!expectedPath.endsWith(".json")) continue; ··· 63 const hasInput = Object.keys(inputFiles).includes(inputPath); 64 65 if (hasInput) { 66 + it(`should emit ${expectedPath}`, async function () { 67 + // Compile each file in isolation 68 + const emitResult = await doEmit({ [inputPath]: inputFiles[inputPath] }, inputPath); 69 + 70 // Check for compilation errors 71 if (emitResult.diagnostics.length > 0) { 72 const formattedDiagnostics = emitResult.diagnostics.map((diag) => ··· 77 ); 78 } 79 80 + const normalizedExpectedPath = normalizePathToPosix(expectedPath); 81 + 82 assert.ok( 83 Object.prototype.hasOwnProperty.call( 84 emitResult.files, 85 + normalizedExpectedPath, 86 ), 87 `Expected file ${expectedPath} was not produced`, 88 ); 89 90 + const actual = JSON.parse(emitResult.files[normalizedExpectedPath]); 91 const expected = JSON.parse(expectedFiles[expectedPath]); 92 assert.deepStrictEqual(actual, expected); 93 }); ··· 168 await walk(dir, ""); 169 return result; 170 } 171 + 172 + function normalizePathToPosix(thePath: string): string { 173 + return thePath.replaceAll(path.sep, path.posix.sep); 174 + }
-22
packages/emitter/test/spec/basic/input/com/example/unionWithTokens.tsp
··· 1 - import "@typelex/emitter"; 2 - 3 - namespace com.example.unionWithTokens { 4 - /** Tests union with token references */ 5 - model Main { 6 - /** Reason can be a known token or unknown type */ 7 - @required 8 - reason: (ReasonSpam | ReasonViolation | ReasonMisleading | unknown); 9 - } 10 - 11 - /** Spam: frequent unwanted promotion */ 12 - @token 13 - model ReasonSpam {} 14 - 15 - /** Direct violation of rules */ 16 - @token 17 - model ReasonViolation {} 18 - 19 - /** Misleading or deceptive content */ 20 - @token 21 - model ReasonMisleading {} 22 - }
···
+21
packages/emitter/test/spec/basic/output/com/example/other.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.example.other", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "properties": {} 8 + }, 9 + "someDef": { 10 + "type": "object", 11 + "required": [ 12 + "value" 13 + ], 14 + "properties": { 15 + "value": { 16 + "type": "string" 17 + } 18 + } 19 + } 20 + } 21 + }
-34
packages/emitter/test/spec/basic/output/com/example/unionWithTokens.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "com.example.unionWithTokens", 4 - "defs": { 5 - "main": { 6 - "type": "object", 7 - "description": "Tests union with token references", 8 - "required": ["reason"], 9 - "properties": { 10 - "reason": { 11 - "type": "union", 12 - "description": "Reason can be a known token or unknown type", 13 - "refs": [ 14 - "#reasonSpam", 15 - "#reasonViolation", 16 - "#reasonMisleading" 17 - ] 18 - } 19 - } 20 - }, 21 - "reasonSpam": { 22 - "type": "token", 23 - "description": "Spam: frequent unwanted promotion" 24 - }, 25 - "reasonViolation": { 26 - "type": "token", 27 - "description": "Direct violation of rules" 28 - }, 29 - "reasonMisleading": { 30 - "type": "token", 31 - "description": "Misleading or deceptive content" 32 - } 33 - } 34 - }
···
+8
packages/emitter/test/spec/external/input/test/external.tsp
···
··· 1 + import "@typelex/emitter"; 2 + 3 + @external 4 + namespace test.external { 5 + model Main { } 6 + 7 + model AlsoNotEmitted { } 8 + }
+7
packages/emitter/test/spec/external/input/test/normal.tsp
···
··· 1 + import "@typelex/emitter"; 2 + 3 + namespace test.normal { 4 + model Main { 5 + name?: string; 6 + } 7 + }
+14
packages/emitter/test/spec/external/output/test/normal.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "test.normal", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "properties": { 8 + "name": { 9 + "type": "string" 10 + } 11 + } 12 + } 13 + } 14 + }
+32 -4
packages/emitter/test/spec.test.ts
··· 64 // Create a virtual main.tsp that imports all other files 65 const mainContent = 66 'import "@typelex/emitter";\n' + 67 - tspFiles.map((f) => `import "./${f}";`).join("\n"); 68 const filesWithMain = { ...inputFiles, "main.tsp": mainContent }; 69 emitResult = await doEmit(filesWithMain, "main.tsp"); 70 } else { ··· 91 ); 92 } 93 94 assert.ok( 95 Object.prototype.hasOwnProperty.call( 96 emitResult.files, 97 - expectedPath, 98 ), 99 `Expected file ${expectedPath} was not produced`, 100 ); 101 102 - const actual = JSON.parse(emitResult.files[expectedPath]); 103 const expected = JSON.parse(expectedFiles[expectedPath]); 104 assert.deepStrictEqual(actual, expected); 105 }); 106 } else { 107 - it.skip(`TODO: ${expectedPath} (add ${inputPath})`, function () {}); 108 } 109 } 110 }); 111 } 112 }); ··· 180 await walk(dir, ""); 181 return result; 182 }
··· 64 // Create a virtual main.tsp that imports all other files 65 const mainContent = 66 'import "@typelex/emitter";\n' + 67 + tspFiles.map((f) => `import "./${normalizePathToPosix(f)}";`).join("\n"); 68 const filesWithMain = { ...inputFiles, "main.tsp": mainContent }; 69 emitResult = await doEmit(filesWithMain, "main.tsp"); 70 } else { ··· 91 ); 92 } 93 94 + const normalizedExpectedPath = normalizePathToPosix(expectedPath); 95 + 96 assert.ok( 97 Object.prototype.hasOwnProperty.call( 98 emitResult.files, 99 + normalizedExpectedPath, 100 ), 101 `Expected file ${expectedPath} was not produced`, 102 ); 103 104 + const actual = JSON.parse(emitResult.files[normalizedExpectedPath]); 105 const expected = JSON.parse(expectedFiles[expectedPath]); 106 assert.deepStrictEqual(actual, expected); 107 }); 108 } else { 109 + it(`should emit ${expectedPath}`, function () { 110 + assert.fail( 111 + `Expected output file ${expectedPath} has no corresponding input file ${inputPath}. ` + 112 + `Either add the input file or remove the expected output.` 113 + ); 114 + }); 115 } 116 } 117 + 118 + // Check for unexpected emitted files 119 + it("should not emit unexpected files", function () { 120 + const emittedFiles = Object.keys(emitResult.files).filter(f => f.endsWith(".json")); 121 + const expectedPaths = Object.keys(expectedFiles) 122 + .filter(f => f.endsWith(".json")) 123 + .map(normalizePathToPosix); 124 + 125 + const unexpected = emittedFiles.filter(f => !expectedPaths.includes(f)); 126 + 127 + if (unexpected.length > 0) { 128 + assert.fail( 129 + `Unexpected files were emitted: ${unexpected.join(", ")}. ` + 130 + `Either add expected output files or ensure these should not be emitted.` 131 + ); 132 + } 133 + }); 134 }); 135 } 136 }); ··· 204 await walk(dir, ""); 205 return result; 206 } 207 + 208 + function normalizePathToPosix(thePath: string): string { 209 + return thePath.replaceAll(path.sep, path.posix.sep); 210 + }
+3 -3
packages/example/package.json
··· 5 "type": "module", 6 "scripts": { 7 "build": "pnpm run build:lexicons && pnpm run build:codegen", 8 - "build:lexicons": "tsp compile typelex/main.tsp", 9 - "build:codegen": "lex gen-server --yes ./src lexicons/app/example/*.json" 10 }, 11 "dependencies": { 12 "@atproto/lex-cli": "^0.9.5", 13 "@atproto/xrpc-server": "^0.9.5", 14 - "@typespec/compiler": "^1.4.0", 15 "@typelex/emitter": "workspace:*" 16 }, 17 "devDependencies": {
··· 5 "type": "module", 6 "scripts": { 7 "build": "pnpm run build:lexicons && pnpm run build:codegen", 8 + "build:lexicons": "typelex compile xyz.statusphere.*", 9 + "build:codegen": "lex gen-server --yes ./src lexicons/xyz/statusphere/*.json" 10 }, 11 "dependencies": { 12 "@atproto/lex-cli": "^0.9.5", 13 "@atproto/xrpc-server": "^0.9.5", 14 + "@typelex/cli": "workspace:*", 15 "@typelex/emitter": "workspace:*" 16 }, 17 "devDependencies": {
+45 -6
packages/example/src/index.ts
··· 10 createServer as createXrpcServer, 11 } from '@atproto/xrpc-server' 12 import { schemas } from './lexicons.js' 13 14 export function createServer(options?: XrpcOptions): Server { 15 return new Server(options) ··· 17 18 export class Server { 19 xrpc: XrpcServer 20 - app: AppNS 21 22 constructor(options?: XrpcOptions) { 23 this.xrpc = createXrpcServer(schemas, options) 24 - this.app = new AppNS(this) 25 } 26 } 27 28 - export class AppNS { 29 _server: Server 30 - example: AppExampleNS 31 32 constructor(server: Server) { 33 this._server = server 34 - this.example = new AppExampleNS(server) 35 } 36 } 37 38 - export class AppExampleNS { 39 _server: Server 40 41 constructor(server: Server) { 42 this._server = server 43 } 44 }
··· 10 createServer as createXrpcServer, 11 } from '@atproto/xrpc-server' 12 import { schemas } from './lexicons.js' 13 + import * as XyzStatusphereGetStatuses from './types/xyz/statusphere/getStatuses.js' 14 + import * as XyzStatusphereGetUser from './types/xyz/statusphere/getUser.js' 15 + import * as XyzStatusphereSendStatus from './types/xyz/statusphere/sendStatus.js' 16 17 export function createServer(options?: XrpcOptions): Server { 18 return new Server(options) ··· 20 21 export class Server { 22 xrpc: XrpcServer 23 + xyz: XyzNS 24 25 constructor(options?: XrpcOptions) { 26 this.xrpc = createXrpcServer(schemas, options) 27 + this.xyz = new XyzNS(this) 28 } 29 } 30 31 + export class XyzNS { 32 _server: Server 33 + statusphere: XyzStatusphereNS 34 35 constructor(server: Server) { 36 this._server = server 37 + this.statusphere = new XyzStatusphereNS(server) 38 } 39 } 40 41 + export class XyzStatusphereNS { 42 _server: Server 43 44 constructor(server: Server) { 45 this._server = server 46 + } 47 + 48 + getStatuses<A extends Auth = void>( 49 + cfg: MethodConfigOrHandler< 50 + A, 51 + XyzStatusphereGetStatuses.QueryParams, 52 + XyzStatusphereGetStatuses.HandlerInput, 53 + XyzStatusphereGetStatuses.HandlerOutput 54 + >, 55 + ) { 56 + const nsid = 'xyz.statusphere.getStatuses' // @ts-ignore 57 + return this._server.xrpc.method(nsid, cfg) 58 + } 59 + 60 + getUser<A extends Auth = void>( 61 + cfg: MethodConfigOrHandler< 62 + A, 63 + XyzStatusphereGetUser.QueryParams, 64 + XyzStatusphereGetUser.HandlerInput, 65 + XyzStatusphereGetUser.HandlerOutput 66 + >, 67 + ) { 68 + const nsid = 'xyz.statusphere.getUser' // @ts-ignore 69 + return this._server.xrpc.method(nsid, cfg) 70 + } 71 + 72 + sendStatus<A extends Auth = void>( 73 + cfg: MethodConfigOrHandler< 74 + A, 75 + XyzStatusphereSendStatus.QueryParams, 76 + XyzStatusphereSendStatus.HandlerInput, 77 + XyzStatusphereSendStatus.HandlerOutput 78 + >, 79 + ) { 80 + const nsid = 'xyz.statusphere.sendStatus' // @ts-ignore 81 + return this._server.xrpc.method(nsid, cfg) 82 } 83 }
+100 -153
packages/example/src/lexicons.ts
··· 10 import { type $Typed, is$typed, maybe$typed } from './util.js' 11 12 export const schemaDict = { 13 - AppExampleDefs: { 14 lexicon: 1, 15 - id: 'app.example.defs', 16 defs: { 17 - postRef: { 18 type: 'object', 19 properties: { 20 uri: { 21 type: 'string', 22 - description: 'AT URI of the post', 23 }, 24 - cid: { 25 type: 'string', 26 - description: 'CID of the post', 27 }, 28 - }, 29 - description: 'Reference to a post', 30 - required: ['uri', 'cid'], 31 - }, 32 - replyRef: { 33 - type: 'object', 34 - properties: { 35 - root: { 36 - type: 'ref', 37 - ref: 'lex:app.example.defs#postRef', 38 - description: 'Root post in the thread', 39 }, 40 - parent: { 41 type: 'ref', 42 - ref: 'lex:app.example.defs#postRef', 43 - description: 'Direct parent post being replied to', 44 }, 45 }, 46 - description: 'Reference to a parent post in a reply chain', 47 - required: ['root', 'parent'], 48 }, 49 - entity: { 50 type: 'object', 51 properties: { 52 - start: { 53 - type: 'integer', 54 - description: 'Start index in text', 55 - }, 56 - end: { 57 - type: 'integer', 58 - description: 'End index in text', 59 - }, 60 - type: { 61 type: 'string', 62 - description: 'Entity type', 63 }, 64 - value: { 65 type: 'string', 66 - description: 'Entity value (handle, URL, or tag)', 67 }, 68 }, 69 - description: 'Text entity (mention, link, or tag)', 70 - required: ['start', 'end', 'type', 'value'], 71 - }, 72 - notificationType: { 73 - type: 'string', 74 - knownValues: ['like', 'repost', 'follow', 'mention', 'reply'], 75 - description: 'Type of notification', 76 }, 77 }, 78 }, 79 - AppExampleFollow: { 80 lexicon: 1, 81 - id: 'app.example.follow', 82 defs: { 83 main: { 84 - type: 'record', 85 - key: 'tid', 86 - record: { 87 - type: 'object', 88 properties: { 89 - subject: { 90 - type: 'string', 91 - description: 'DID of the account being followed', 92 - }, 93 - createdAt: { 94 - type: 'string', 95 - format: 'datetime', 96 - description: 'When the follow was created', 97 }, 98 }, 99 - required: ['subject', 'createdAt'], 100 }, 101 - description: 'A follow relationship', 102 - }, 103 - }, 104 - }, 105 - AppExampleLike: { 106 - lexicon: 1, 107 - id: 'app.example.like', 108 - defs: { 109 - main: { 110 - type: 'record', 111 - key: 'tid', 112 - record: { 113 - type: 'object', 114 - properties: { 115 - subject: { 116 - type: 'ref', 117 - ref: 'lex:app.example.defs#postRef', 118 - description: 'Post being liked', 119 }, 120 - createdAt: { 121 - type: 'string', 122 - format: 'datetime', 123 - description: 'When the like was created', 124 - }, 125 }, 126 - required: ['subject', 'createdAt'], 127 }, 128 - description: 'A like on a post', 129 }, 130 }, 131 }, 132 - AppExamplePost: { 133 lexicon: 1, 134 - id: 'app.example.post', 135 defs: { 136 main: { 137 - type: 'record', 138 - key: 'tid', 139 - record: { 140 - type: 'object', 141 - properties: { 142 - text: { 143 - type: 'string', 144 - description: 'Post text content', 145 - }, 146 - createdAt: { 147 - type: 'string', 148 - format: 'datetime', 149 - description: 'Creation timestamp', 150 - }, 151 - langs: { 152 - type: 'array', 153 - items: { 154 - type: 'string', 155 }, 156 - description: 'Languages the post is written in', 157 - }, 158 - entities: { 159 - type: 'array', 160 - items: { 161 type: 'ref', 162 - ref: 'lex:app.example.defs#entity', 163 }, 164 - description: 'Referenced entities in the post', 165 }, 166 - reply: { 167 - type: 'ref', 168 - ref: 'lex:app.example.defs#replyRef', 169 - description: 'Post the user is replying to', 170 - }, 171 }, 172 - required: ['text', 'createdAt'], 173 }, 174 - description: 'A post in the feed', 175 }, 176 }, 177 }, 178 - AppExampleProfile: { 179 lexicon: 1, 180 - id: 'app.example.profile', 181 defs: { 182 main: { 183 - type: 'record', 184 - key: 'self', 185 - record: { 186 - type: 'object', 187 - properties: { 188 - displayName: { 189 - type: 'string', 190 - description: 'Display name', 191 - }, 192 - description: { 193 - type: 'string', 194 - description: 'Profile description', 195 }, 196 - avatar: { 197 - type: 'string', 198 - description: 'Profile avatar image', 199 }, 200 - banner: { 201 - type: 'string', 202 - description: 'Profile banner image', 203 - }, 204 }, 205 }, 206 - description: 'User profile information', 207 }, 208 }, 209 }, 210 - AppExampleRepost: { 211 lexicon: 1, 212 - id: 'app.example.repost', 213 defs: { 214 main: { 215 type: 'record', ··· 217 record: { 218 type: 'object', 219 properties: { 220 - subject: { 221 - type: 'ref', 222 - ref: 'lex:app.example.defs#postRef', 223 - description: 'Post being reposted', 224 }, 225 createdAt: { 226 type: 'string', 227 format: 'datetime', 228 - description: 'When the repost was created', 229 }, 230 }, 231 - required: ['subject', 'createdAt'], 232 }, 233 - description: 'A repost of another post', 234 }, 235 }, 236 }, ··· 267 } 268 269 export const ids = { 270 - AppExampleDefs: 'app.example.defs', 271 - AppExampleFollow: 'app.example.follow', 272 - AppExampleLike: 'app.example.like', 273 - AppExamplePost: 'app.example.post', 274 - AppExampleProfile: 'app.example.profile', 275 - AppExampleRepost: 'app.example.repost', 276 } as const
··· 10 import { type $Typed, is$typed, maybe$typed } from './util.js' 11 12 export const schemaDict = { 13 + XyzStatusphereDefs: { 14 lexicon: 1, 15 + id: 'xyz.statusphere.defs', 16 defs: { 17 + statusView: { 18 type: 'object', 19 properties: { 20 uri: { 21 type: 'string', 22 + format: 'at-uri', 23 }, 24 + status: { 25 type: 'string', 26 + maxLength: 32, 27 + minLength: 1, 28 + maxGraphemes: 1, 29 }, 30 + createdAt: { 31 + type: 'string', 32 + format: 'datetime', 33 }, 34 + profile: { 35 type: 'ref', 36 + ref: 'lex:xyz.statusphere.defs#profileView', 37 }, 38 }, 39 + required: ['uri', 'status', 'createdAt', 'profile'], 40 }, 41 + profileView: { 42 type: 'object', 43 properties: { 44 + did: { 45 type: 'string', 46 + format: 'did', 47 }, 48 + handle: { 49 type: 'string', 50 + format: 'handle', 51 }, 52 }, 53 + required: ['did', 'handle'], 54 }, 55 }, 56 }, 57 + XyzStatusphereGetStatuses: { 58 lexicon: 1, 59 + id: 'xyz.statusphere.getStatuses', 60 defs: { 61 main: { 62 + type: 'query', 63 + description: 'Get a list of the most recent statuses on the network.', 64 + parameters: { 65 + type: 'params', 66 properties: { 67 + limit: { 68 + type: 'integer', 69 + minimum: 1, 70 + maximum: 100, 71 + default: 50, 72 }, 73 }, 74 }, 75 + output: { 76 + encoding: 'application/json', 77 + schema: { 78 + type: 'object', 79 + properties: { 80 + statuses: { 81 + type: 'array', 82 + items: { 83 + type: 'ref', 84 + ref: 'lex:xyz.statusphere.defs#statusView', 85 + }, 86 + }, 87 }, 88 + required: ['statuses'], 89 }, 90 }, 91 }, 92 }, 93 }, 94 + XyzStatusphereGetUser: { 95 lexicon: 1, 96 + id: 'xyz.statusphere.getUser', 97 defs: { 98 main: { 99 + type: 'query', 100 + description: "Get the current user's profile and status.", 101 + output: { 102 + encoding: 'application/json', 103 + schema: { 104 + type: 'object', 105 + properties: { 106 + profile: { 107 + type: 'ref', 108 + ref: 'lex:app.bsky.actor.defs#profileView', 109 }, 110 + status: { 111 type: 'ref', 112 + ref: 'lex:xyz.statusphere.defs#statusView', 113 }, 114 }, 115 + required: ['profile'], 116 }, 117 }, 118 }, 119 }, 120 }, 121 + XyzStatusphereSendStatus: { 122 lexicon: 1, 123 + id: 'xyz.statusphere.sendStatus', 124 defs: { 125 main: { 126 + type: 'procedure', 127 + description: 'Send a status into the ATmosphere.', 128 + input: { 129 + encoding: 'application/json', 130 + schema: { 131 + type: 'object', 132 + properties: { 133 + status: { 134 + type: 'string', 135 + maxLength: 32, 136 + minLength: 1, 137 + maxGraphemes: 1, 138 + }, 139 }, 140 + required: ['status'], 141 + }, 142 + }, 143 + output: { 144 + encoding: 'application/json', 145 + schema: { 146 + type: 'object', 147 + properties: { 148 + status: { 149 + type: 'ref', 150 + ref: 'lex:xyz.statusphere.defs#statusView', 151 + }, 152 }, 153 + required: ['status'], 154 }, 155 }, 156 }, 157 }, 158 }, 159 + XyzStatusphereStatus: { 160 lexicon: 1, 161 + id: 'xyz.statusphere.status', 162 defs: { 163 main: { 164 type: 'record', ··· 166 record: { 167 type: 'object', 168 properties: { 169 + status: { 170 + type: 'string', 171 + maxLength: 32, 172 + minLength: 1, 173 + maxGraphemes: 1, 174 }, 175 createdAt: { 176 type: 'string', 177 format: 'datetime', 178 }, 179 }, 180 + required: ['status', 'createdAt'], 181 }, 182 }, 183 }, 184 }, ··· 215 } 216 217 export const ids = { 218 + XyzStatusphereDefs: 'xyz.statusphere.defs', 219 + XyzStatusphereGetStatuses: 'xyz.statusphere.getStatuses', 220 + XyzStatusphereGetUser: 'xyz.statusphere.getUser', 221 + XyzStatusphereSendStatus: 'xyz.statusphere.sendStatus', 222 + XyzStatusphereStatus: 'xyz.statusphere.status', 223 } as const
-79
packages/example/src/types/app/example/defs.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { CID } from 'multiformats/cid' 6 - import { validate as _validate } from '../../../lexicons' 7 - import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 - 9 - const is$typed = _is$typed, 10 - validate = _validate 11 - const id = 'app.example.defs' 12 - 13 - /** Reference to a post */ 14 - export interface PostRef { 15 - $type?: 'app.example.defs#postRef' 16 - /** AT URI of the post */ 17 - uri: string 18 - /** CID of the post */ 19 - cid: string 20 - } 21 - 22 - const hashPostRef = 'postRef' 23 - 24 - export function isPostRef<V>(v: V) { 25 - return is$typed(v, id, hashPostRef) 26 - } 27 - 28 - export function validatePostRef<V>(v: V) { 29 - return validate<PostRef & V>(v, id, hashPostRef) 30 - } 31 - 32 - /** Reference to a parent post in a reply chain */ 33 - export interface ReplyRef { 34 - $type?: 'app.example.defs#replyRef' 35 - root: PostRef 36 - parent: PostRef 37 - } 38 - 39 - const hashReplyRef = 'replyRef' 40 - 41 - export function isReplyRef<V>(v: V) { 42 - return is$typed(v, id, hashReplyRef) 43 - } 44 - 45 - export function validateReplyRef<V>(v: V) { 46 - return validate<ReplyRef & V>(v, id, hashReplyRef) 47 - } 48 - 49 - /** Text entity (mention, link, or tag) */ 50 - export interface Entity { 51 - $type?: 'app.example.defs#entity' 52 - /** Start index in text */ 53 - start: number 54 - /** End index in text */ 55 - end: number 56 - /** Entity type */ 57 - type: string 58 - /** Entity value (handle, URL, or tag) */ 59 - value: string 60 - } 61 - 62 - const hashEntity = 'entity' 63 - 64 - export function isEntity<V>(v: V) { 65 - return is$typed(v, id, hashEntity) 66 - } 67 - 68 - export function validateEntity<V>(v: V) { 69 - return validate<Entity & V>(v, id, hashEntity) 70 - } 71 - 72 - /** Type of notification */ 73 - export type NotificationType = 74 - | 'like' 75 - | 'repost' 76 - | 'follow' 77 - | 'mention' 78 - | 'reply' 79 - | (string & {})
···
-30
packages/example/src/types/app/example/follow.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { CID } from 'multiformats/cid' 6 - import { validate as _validate } from '../../../lexicons' 7 - import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 - 9 - const is$typed = _is$typed, 10 - validate = _validate 11 - const id = 'app.example.follow' 12 - 13 - export interface Record { 14 - $type: 'app.example.follow' 15 - /** DID of the account being followed */ 16 - subject: string 17 - /** When the follow was created */ 18 - createdAt: string 19 - [k: string]: unknown 20 - } 21 - 22 - const hashRecord = 'main' 23 - 24 - export function isRecord<V>(v: V) { 25 - return is$typed(v, id, hashRecord) 26 - } 27 - 28 - export function validateRecord<V>(v: V) { 29 - return validate<Record & V>(v, id, hashRecord, true) 30 - }
···
-30
packages/example/src/types/app/example/like.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { CID } from 'multiformats/cid' 6 - import { validate as _validate } from '../../../lexicons' 7 - import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 - import type * as AppExampleDefs from './defs.js' 9 - 10 - const is$typed = _is$typed, 11 - validate = _validate 12 - const id = 'app.example.like' 13 - 14 - export interface Record { 15 - $type: 'app.example.like' 16 - subject: AppExampleDefs.PostRef 17 - /** When the like was created */ 18 - createdAt: string 19 - [k: string]: unknown 20 - } 21 - 22 - const hashRecord = 'main' 23 - 24 - export function isRecord<V>(v: V) { 25 - return is$typed(v, id, hashRecord) 26 - } 27 - 28 - export function validateRecord<V>(v: V) { 29 - return validate<Record & V>(v, id, hashRecord, true) 30 - }
···
-36
packages/example/src/types/app/example/post.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { CID } from 'multiformats/cid' 6 - import { validate as _validate } from '../../../lexicons' 7 - import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 - import type * as AppExampleDefs from './defs.js' 9 - 10 - const is$typed = _is$typed, 11 - validate = _validate 12 - const id = 'app.example.post' 13 - 14 - export interface Record { 15 - $type: 'app.example.post' 16 - /** Post text content */ 17 - text: string 18 - /** Creation timestamp */ 19 - createdAt: string 20 - /** Languages the post is written in */ 21 - langs?: string[] 22 - /** Referenced entities in the post */ 23 - entities?: AppExampleDefs.Entity[] 24 - reply?: AppExampleDefs.ReplyRef 25 - [k: string]: unknown 26 - } 27 - 28 - const hashRecord = 'main' 29 - 30 - export function isRecord<V>(v: V) { 31 - return is$typed(v, id, hashRecord) 32 - } 33 - 34 - export function validateRecord<V>(v: V) { 35 - return validate<Record & V>(v, id, hashRecord, true) 36 - }
···
-34
packages/example/src/types/app/example/profile.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { CID } from 'multiformats/cid' 6 - import { validate as _validate } from '../../../lexicons' 7 - import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 - 9 - const is$typed = _is$typed, 10 - validate = _validate 11 - const id = 'app.example.profile' 12 - 13 - export interface Record { 14 - $type: 'app.example.profile' 15 - /** Display name */ 16 - displayName?: string 17 - /** Profile description */ 18 - description?: string 19 - /** Profile avatar image */ 20 - avatar?: string 21 - /** Profile banner image */ 22 - banner?: string 23 - [k: string]: unknown 24 - } 25 - 26 - const hashRecord = 'main' 27 - 28 - export function isRecord<V>(v: V) { 29 - return is$typed(v, id, hashRecord) 30 - } 31 - 32 - export function validateRecord<V>(v: V) { 33 - return validate<Record & V>(v, id, hashRecord, true) 34 - }
···
-30
packages/example/src/types/app/example/repost.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { CID } from 'multiformats/cid' 6 - import { validate as _validate } from '../../../lexicons' 7 - import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 - import type * as AppExampleDefs from './defs.js' 9 - 10 - const is$typed = _is$typed, 11 - validate = _validate 12 - const id = 'app.example.repost' 13 - 14 - export interface Record { 15 - $type: 'app.example.repost' 16 - subject: AppExampleDefs.PostRef 17 - /** When the repost was created */ 18 - createdAt: string 19 - [k: string]: unknown 20 - } 21 - 22 - const hashRecord = 'main' 23 - 24 - export function isRecord<V>(v: V) { 25 - return is$typed(v, id, hashRecord) 26 - } 27 - 28 - export function validateRecord<V>(v: V) { 29 - return validate<Record & V>(v, id, hashRecord, true) 30 - }
···
+45
packages/example/src/types/xyz/statusphere/defs.ts
···
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../lexicons' 7 + import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + 9 + const is$typed = _is$typed, 10 + validate = _validate 11 + const id = 'xyz.statusphere.defs' 12 + 13 + export interface StatusView { 14 + $type?: 'xyz.statusphere.defs#statusView' 15 + uri: string 16 + status: string 17 + createdAt: string 18 + profile: ProfileView 19 + } 20 + 21 + const hashStatusView = 'statusView' 22 + 23 + export function isStatusView<V>(v: V) { 24 + return is$typed(v, id, hashStatusView) 25 + } 26 + 27 + export function validateStatusView<V>(v: V) { 28 + return validate<StatusView & V>(v, id, hashStatusView) 29 + } 30 + 31 + export interface ProfileView { 32 + $type?: 'xyz.statusphere.defs#profileView' 33 + did: string 34 + handle: string 35 + } 36 + 37 + const hashProfileView = 'profileView' 38 + 39 + export function isProfileView<V>(v: V) { 40 + return is$typed(v, id, hashProfileView) 41 + } 42 + 43 + export function validateProfileView<V>(v: V) { 44 + return validate<ProfileView & V>(v, id, hashProfileView) 45 + }
+36
packages/example/src/types/xyz/statusphere/getStatuses.ts
···
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../lexicons' 7 + import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + import type * as XyzStatusphereDefs from './defs.js' 9 + 10 + const is$typed = _is$typed, 11 + validate = _validate 12 + const id = 'xyz.statusphere.getStatuses' 13 + 14 + export type QueryParams = { 15 + limit: number 16 + } 17 + export type InputSchema = undefined 18 + 19 + export interface OutputSchema { 20 + statuses: XyzStatusphereDefs.StatusView[] 21 + } 22 + 23 + export type HandlerInput = void 24 + 25 + export interface HandlerSuccess { 26 + encoding: 'application/json' 27 + body: OutputSchema 28 + headers?: { [key: string]: string } 29 + } 30 + 31 + export interface HandlerError { 32 + status: number 33 + message?: string 34 + } 35 + 36 + export type HandlerOutput = HandlerError | HandlerSuccess
+36
packages/example/src/types/xyz/statusphere/getUser.ts
···
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../lexicons' 7 + import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + import type * as AppBskyActorDefs from '../../app/bsky/actor/defs.js' 9 + import type * as XyzStatusphereDefs from './defs.js' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'xyz.statusphere.getUser' 14 + 15 + export type QueryParams = {} 16 + export type InputSchema = undefined 17 + 18 + export interface OutputSchema { 19 + profile: AppBskyActorDefs.ProfileView 20 + status?: XyzStatusphereDefs.StatusView 21 + } 22 + 23 + export type HandlerInput = void 24 + 25 + export interface HandlerSuccess { 26 + encoding: 'application/json' 27 + body: OutputSchema 28 + headers?: { [key: string]: string } 29 + } 30 + 31 + export interface HandlerError { 32 + status: number 33 + message?: string 34 + } 35 + 36 + export type HandlerOutput = HandlerError | HandlerSuccess
+40
packages/example/src/types/xyz/statusphere/sendStatus.ts
···
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../lexicons' 7 + import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + import type * as XyzStatusphereDefs from './defs.js' 9 + 10 + const is$typed = _is$typed, 11 + validate = _validate 12 + const id = 'xyz.statusphere.sendStatus' 13 + 14 + export type QueryParams = {} 15 + 16 + export interface InputSchema { 17 + status: string 18 + } 19 + 20 + export interface OutputSchema { 21 + status: XyzStatusphereDefs.StatusView 22 + } 23 + 24 + export interface HandlerInput { 25 + encoding: 'application/json' 26 + body: InputSchema 27 + } 28 + 29 + export interface HandlerSuccess { 30 + encoding: 'application/json' 31 + body: OutputSchema 32 + headers?: { [key: string]: string } 33 + } 34 + 35 + export interface HandlerError { 36 + status: number 37 + message?: string 38 + } 39 + 40 + export type HandlerOutput = HandlerError | HandlerSuccess
+28
packages/example/src/types/xyz/statusphere/status.ts
···
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../lexicons' 7 + import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + 9 + const is$typed = _is$typed, 10 + validate = _validate 11 + const id = 'xyz.statusphere.status' 12 + 13 + export interface Record { 14 + $type: 'xyz.statusphere.status' 15 + status: string 16 + createdAt: string 17 + [k: string]: unknown 18 + } 19 + 20 + const hashRecord = 'main' 21 + 22 + export function isRecord<V>(v: V) { 23 + return is$typed(v, id, hashRecord) 24 + } 25 + 26 + export function validateRecord<V>(v: V) { 27 + return validate<Record & V>(v, id, hashRecord, true) 28 + }
-5
packages/example/tspconfig.yaml
··· 1 - emit: 2 - - "@typelex/emitter" 3 - options: 4 - "@typelex/emitter": 5 - output-dir: "./lexicons"
···
+235
packages/example/typelex/externals.tsp
···
··· 1 + import "@typelex/emitter"; 2 + 3 + // Generated by typelex 4 + // This file is auto-generated. Do not edit manually. 5 + 6 + @external 7 + namespace app.bsky.actor.defs { 8 + model AdultContentPref { } 9 + model BskyAppProgressGuide { } 10 + model BskyAppStatePref { } 11 + model ContentLabelPref { } 12 + model FeedViewPref { } 13 + model HiddenPostsPref { } 14 + model InterestsPref { } 15 + model KnownFollowers { } 16 + model LabelerPrefItem { } 17 + model LabelersPref { } 18 + model MutedWord { } 19 + model MutedWordsPref { } 20 + model MutedWordTarget { } 21 + model Nux { } 22 + model PersonalDetailsPref { } 23 + model PostInteractionSettingsPref { } 24 + model ProfileAssociated { } 25 + model ProfileAssociatedChat { } 26 + model ProfileView { } 27 + model ProfileViewBasic { } 28 + model ProfileViewDetailed { } 29 + model SavedFeed { } 30 + model SavedFeedsPref { } 31 + model SavedFeedsPrefV2 { } 32 + model ThreadViewPref { } 33 + model ViewerState { } 34 + } 35 + 36 + @external 37 + namespace app.bsky.actor.profile { 38 + model Main { } 39 + } 40 + 41 + @external 42 + namespace app.bsky.embed.defs { 43 + model AspectRatio { } 44 + } 45 + 46 + @external 47 + namespace app.bsky.embed.external { 48 + model External { } 49 + model Main { } 50 + model View { } 51 + model ViewExternal { } 52 + } 53 + 54 + @external 55 + namespace app.bsky.embed.images { 56 + model Image { } 57 + model Main { } 58 + model View { } 59 + model ViewImage { } 60 + } 61 + 62 + @external 63 + namespace app.bsky.embed.`record` { 64 + model Main { } 65 + model View { } 66 + model ViewBlocked { } 67 + model ViewDetached { } 68 + model ViewNotFound { } 69 + model ViewRecord { } 70 + } 71 + 72 + @external 73 + namespace app.bsky.embed.recordWithMedia { 74 + model Main { } 75 + model View { } 76 + } 77 + 78 + @external 79 + namespace app.bsky.embed.video { 80 + model Caption { } 81 + model Main { } 82 + model View { } 83 + } 84 + 85 + @external 86 + namespace app.bsky.feed.defs { 87 + model BlockedAuthor { } 88 + model BlockedPost { } 89 + @token model ClickthroughAuthor { } 90 + @token model ClickthroughEmbed { } 91 + @token model ClickthroughItem { } 92 + @token model ClickthroughReposter { } 93 + @token model ContentModeUnspecified { } 94 + @token model ContentModeVideo { } 95 + model FeedViewPost { } 96 + model GeneratorView { } 97 + model GeneratorViewerState { } 98 + model Interaction { } 99 + @token model InteractionLike { } 100 + @token model InteractionQuote { } 101 + @token model InteractionReply { } 102 + @token model InteractionRepost { } 103 + @token model InteractionSeen { } 104 + @token model InteractionShare { } 105 + model NotFoundPost { } 106 + model PostView { } 107 + model ReasonPin { } 108 + model ReasonRepost { } 109 + model ReplyRef { } 110 + @token model RequestLess { } 111 + @token model RequestMore { } 112 + model SkeletonFeedPost { } 113 + model SkeletonReasonPin { } 114 + model SkeletonReasonRepost { } 115 + model ThreadContext { } 116 + model ThreadgateView { } 117 + model ThreadViewPost { } 118 + model ViewerState { } 119 + } 120 + 121 + @external 122 + namespace app.bsky.feed.postgate { 123 + model DisableRule { } 124 + model Main { } 125 + } 126 + 127 + @external 128 + namespace app.bsky.feed.threadgate { 129 + model FollowerRule { } 130 + model FollowingRule { } 131 + model ListRule { } 132 + model Main { } 133 + model MentionRule { } 134 + } 135 + 136 + @external 137 + namespace app.bsky.graph.defs { 138 + @token model Curatelist { } 139 + model ListItemView { } 140 + model ListPurpose { } 141 + model ListView { } 142 + model ListViewBasic { } 143 + model ListViewerState { } 144 + @token model Modlist { } 145 + model NotFoundActor { } 146 + @token model Referencelist { } 147 + model Relationship { } 148 + model StarterPackView { } 149 + model StarterPackViewBasic { } 150 + } 151 + 152 + @external 153 + namespace app.bsky.labeler.defs { 154 + model LabelerPolicies { } 155 + model LabelerView { } 156 + model LabelerViewDetailed { } 157 + model LabelerViewerState { } 158 + } 159 + 160 + @external 161 + namespace app.bsky.richtext.facet { 162 + model ByteSlice { } 163 + model Link { } 164 + model Main { } 165 + model Mention { } 166 + model Tag { } 167 + } 168 + 169 + @external 170 + namespace com.atproto.label.defs { 171 + model Label { } 172 + model LabelValue { } 173 + model LabelValueDefinition { } 174 + model LabelValueDefinitionStrings { } 175 + model SelfLabel { } 176 + model SelfLabels { } 177 + } 178 + 179 + @external 180 + namespace com.atproto.repo.applyWrites { 181 + model Create { } 182 + model CreateResult { } 183 + model Delete { } 184 + model DeleteResult { } 185 + model Update { } 186 + model UpdateResult { } 187 + } 188 + 189 + @external 190 + namespace com.atproto.repo.createRecord { 191 + } 192 + 193 + @external 194 + namespace com.atproto.repo.defs { 195 + model CommitMeta { } 196 + } 197 + 198 + @external 199 + namespace com.atproto.repo.deleteRecord { 200 + } 201 + 202 + @external 203 + namespace com.atproto.repo.describeRepo { 204 + } 205 + 206 + @external 207 + namespace com.atproto.repo.getRecord { 208 + } 209 + 210 + @external 211 + namespace com.atproto.repo.importRepo { 212 + } 213 + 214 + @external 215 + namespace com.atproto.repo.listMissingBlobs { 216 + model RecordBlob { } 217 + } 218 + 219 + @external 220 + namespace com.atproto.repo.listRecords { 221 + model Record { } 222 + } 223 + 224 + @external 225 + namespace com.atproto.repo.putRecord { 226 + } 227 + 228 + @external 229 + namespace com.atproto.repo.strongRef { 230 + model Main { } 231 + } 232 + 233 + @external 234 + namespace com.atproto.repo.uploadBlob { 235 + }
+46 -122
packages/example/typelex/main.tsp
··· 1 import "@typelex/emitter"; 2 - 3 - // Example showing typelex as source of truth for atproto lexicons 4 - 5 - // ============ Common Types ============ 6 - 7 - namespace app.example.defs { 8 - @doc("Type of notification") 9 - union notificationType { 10 - string, 11 - 12 - Like: "like", 13 - Repost: "repost", 14 - Follow: "follow", 15 - Mention: "mention", 16 - Reply: "reply", 17 - } 18 - 19 - @doc("Reference to a post") 20 - model PostRef { 21 - @doc("AT URI of the post") 22 - @required 23 - uri: string; 24 - 25 - @doc("CID of the post") 26 - @required 27 - cid: string; 28 - } 29 - 30 - @doc("Reference to a parent post in a reply chain") 31 - model ReplyRef { 32 - @doc("Root post in the thread") 33 - @required 34 - root: PostRef; 35 - 36 - @doc("Direct parent post being replied to") 37 - @required 38 - parent: PostRef; 39 - } 40 - 41 - @doc("Text entity (mention, link, or tag)") 42 - model Entity { 43 - @doc("Start index in text") 44 - @required 45 - start: int32; 46 47 - @doc("End index in text") 48 - @required 49 - end: int32; 50 51 - @doc("Entity type") 52 @required 53 - type: string; 54 55 - @doc("Entity value (handle, URL, or tag)") 56 - @required 57 - value: string; 58 } 59 - } 60 61 - // ============ Records ============ 62 - 63 - namespace app.example.post { 64 - @rec("tid") 65 - @doc("A post in the feed") 66 - model Main { 67 - @doc("Post text content") 68 - @required 69 - text: string; 70 - 71 - @doc("Creation timestamp") 72 - @required 73 - createdAt: datetime; 74 - 75 - @doc("Languages the post is written in") 76 - langs?: string[]; 77 - 78 - @doc("Referenced entities in the post") 79 - entities?: app.example.defs.Entity[]; 80 - 81 - @doc("Post the user is replying to") 82 - reply?: app.example.defs.ReplyRef; 83 } 84 } 85 86 - namespace app.example.follow { 87 @rec("tid") 88 - @doc("A follow relationship") 89 model Main { 90 - @doc("DID of the account being followed") 91 @required 92 - subject: string; 93 94 - @doc("When the follow was created") 95 - @required 96 - createdAt: datetime; 97 } 98 } 99 100 - namespace app.example.like { 101 - @rec("tid") 102 - @doc("A like on a post") 103 - model Main { 104 - @doc("Post being liked") 105 - @required 106 - subject: app.example.defs.PostRef; 107 - 108 - @doc("When the like was created") 109 - @required 110 - createdAt: datetime; 111 - } 112 } 113 114 - namespace app.example.repost { 115 - @rec("tid") 116 - @doc("A repost of another post") 117 - model Main { 118 - @doc("Post being reposted") 119 - @required 120 - subject: app.example.defs.PostRef; 121 - 122 - @doc("When the repost was created") 123 - @required 124 - createdAt: datetime; 125 - } 126 } 127 128 - namespace app.example.profile { 129 - @rec("self") 130 - @doc("User profile information") 131 - model Main { 132 - @doc("Display name") 133 - displayName?: string; 134 - 135 - @doc("Profile description") 136 - description?: string; 137 - 138 - @doc("Profile avatar image") 139 - avatar?: string; 140 - 141 - @doc("Profile banner image") 142 - banner?: string; 143 - } 144 }
··· 1 import "@typelex/emitter"; 2 + import "./externals.tsp"; 3 4 + namespace xyz.statusphere.defs { 5 + model StatusView { 6 + @required uri: atUri; 7 8 @required 9 + @minLength(1) 10 + @maxGraphemes(1) 11 + @maxLength(32) 12 + status: string; 13 14 + @required createdAt: datetime; 15 + @required profile: ProfileView; 16 } 17 18 + model ProfileView { 19 + @required did: did; 20 + @required handle: handle; 21 } 22 } 23 24 + namespace xyz.statusphere.status { 25 @rec("tid") 26 model Main { 27 @required 28 + @minLength(1) 29 + @maxGraphemes(1) 30 + @maxLength(32) 31 + status: string; 32 33 + @required createdAt: datetime; 34 } 35 } 36 37 + namespace xyz.statusphere.sendStatus { 38 + @procedure 39 + @doc("Send a status into the ATmosphere.") 40 + op main( 41 + input: { 42 + @required 43 + @minLength(1) 44 + @maxGraphemes(1) 45 + @maxLength(32) 46 + status: string; 47 + }, 48 + ): { 49 + @required status: xyz.statusphere.defs.StatusView; 50 + }; 51 } 52 53 + namespace xyz.statusphere.getStatuses { 54 + @query 55 + @doc("Get a list of the most recent statuses on the network.") 56 + op main(@minValue(1) @maxValue(100) limit?: integer = 50): { 57 + @required statuses: xyz.statusphere.defs.StatusView[]; 58 + }; 59 } 60 61 + namespace xyz.statusphere.getUser { 62 + @query 63 + @doc("Get the current user's profile and status.") 64 + op main(): { 65 + @required profile: app.bsky.actor.defs.ProfileView; 66 + status?: xyz.statusphere.defs.StatusView; 67 + }; 68 }
+2 -3
packages/playground/package.json
··· 4 "private": true, 5 "type": "module", 6 "scripts": { 7 - "build:samples": "node samples/build.js", 8 - "dev": "npm run build:samples && vite", 9 - "build": "npm run build:samples && vite build", 10 "preview": "vite preview" 11 }, 12 "dependencies": {
··· 4 "private": true, 5 "type": "module", 6 "scripts": { 7 + "dev": "vite", 8 + "build": "vite build", 9 "preview": "vite preview" 10 }, 11 "dependencies": {
-33
packages/playground/samples/build.js
··· 1 - // @ts-check 2 - import { writeFileSync, mkdirSync } from "fs"; 3 - import { dirname, resolve, join } from "path"; 4 - import { fileURLToPath } from "url"; 5 - import { lexicons, bundleLexicon } from "./index.js"; 6 - 7 - const __dirname = dirname(fileURLToPath(import.meta.url)); 8 - const outputDir = resolve(__dirname, "dist"); 9 - 10 - // Create output directory 11 - mkdirSync(outputDir, { recursive: true }); 12 - 13 - // Write each bundled lexicon to disk 14 - const samplesList = {}; 15 - 16 - for (const [namespace, lexicon] of lexicons) { 17 - const bundled = bundleLexicon(namespace); 18 - const filename = `${namespace}.tsp`; 19 - const filepath = join(outputDir, filename); 20 - 21 - writeFileSync(filepath, bundled); 22 - 23 - samplesList[namespace] = { 24 - filename: `samples/dist/${filename}`, 25 - preferredEmitter: "@typelex/emitter", 26 - }; 27 - } 28 - 29 - // Write the samples index 30 - const samplesIndex = `export default ${JSON.stringify(samplesList, null, 2)};`; 31 - writeFileSync(join(outputDir, "samples.js"), samplesIndex); 32 - 33 - console.log(`Wrote ${Object.keys(samplesList).length} bundled samples to disk`);
···
+28 -105
packages/playground/samples/index.js
··· 6 const __dirname = dirname(fileURLToPath(import.meta.url)); 7 8 // Get all tsp files 9 - function getAllTspFiles(dir, baseDir = dir) { 10 const files = []; 11 const entries = readdirSync(dir); 12 ··· 15 const stat = statSync(fullPath); 16 17 if (stat.isDirectory()) { 18 - files.push(...getAllTspFiles(fullPath, baseDir)); 19 } else if (entry.endsWith(".tsp")) { 20 files.push(relative(baseDir, fullPath)); 21 } ··· 24 return files.sort(); 25 } 26 27 - // Extract dependencies from a file 28 - function extractDependencies(content) { 29 - const deps = new Set(); 30 - // Match namespace references like "com.atproto.label.defs.Label" or "com.atproto.repo.strongRef.Main" 31 - // Pattern: word.word.word... followed by dot and identifier starting with capital letter 32 - const pattern = 33 - /\b([a-z]+(?:\.[a-z]+)+(?:\.[a-z][a-zA-Z]*)*)\.[A-Z][a-zA-Z]*/g; 34 - const withoutDeclaration = content.replace(/namespace\s+[a-z.]+\s*\{/, ""); 35 36 - const matches = withoutDeclaration.matchAll(pattern); 37 - for (const match of matches) { 38 - deps.add(match[1]); 39 - } 40 41 - return Array.from(deps); 42 - } 43 44 - const atprotoInputDir = join( 45 - __dirname, 46 - "../../emitter/test/integration/atproto/input", 47 - ); 48 - const lexiconExamplesDir = join( 49 - __dirname, 50 - "../../emitter/test/integration/lexicon-examples/input", 51 - ); 52 53 - const atprotoFiles = getAllTspFiles(atprotoInputDir); 54 - const lexiconExampleFiles = getAllTspFiles(lexiconExamplesDir); 55 56 - // Build dependency graph 57 - const lexicons = new Map(); // namespace -> { file, content, deps } 58 - 59 - // Process atproto files 60 - for (const file of atprotoFiles) { 61 - const fullPath = join(atprotoInputDir, file); 62 - const content = readFileSync(fullPath, "utf-8"); 63 - const namespace = file.replace(/\.tsp$/, "").replace(/\//g, "."); 64 - const deps = extractDependencies(content); 65 - 66 - lexicons.set(namespace, { file: `atproto/${file}`, content, deps }); 67 - } 68 - 69 - // Process lexicon-examples files 70 - for (const file of lexiconExampleFiles) { 71 - const fullPath = join(lexiconExamplesDir, file); 72 - const content = readFileSync(fullPath, "utf-8"); 73 - const namespace = file.replace(/\.tsp$/, "").replace(/\//g, "."); 74 - const deps = extractDependencies(content); 75 - 76 - lexicons.set(namespace, { file: `examples/${file}`, content, deps }); 77 - } 78 - 79 - // Recursively collect all dependencies (topological sort) 80 - function collectDependencies( 81 - namespace, 82 - collected = new Set(), 83 - visiting = new Set(), 84 - ) { 85 - if (collected.has(namespace)) return; 86 - if (visiting.has(namespace)) return; // circular dependency 87 - 88 - const lexicon = lexicons.get(namespace); 89 - if (!lexicon) return; 90 - 91 - visiting.add(namespace); 92 - 93 - // First collect all dependencies 94 - for (const dep of lexicon.deps) { 95 - collectDependencies(dep, collected, visiting); 96 } 97 - 98 - visiting.delete(namespace); 99 - collected.add(namespace); 100 } 101 102 - // Bundle a lexicon with all its dependencies 103 - function bundleLexicon(namespace) { 104 - const collected = new Set(); 105 - collectDependencies(namespace, collected); 106 - 107 - // Put the main lexicon FIRST, then its dependencies 108 - const mainLexicon = lexicons.get(namespace); 109 - const deps = Array.from(collected).filter((ns) => ns !== namespace); 110 - 111 - let bundled = 'import "@typelex/emitter";\n\n'; 112 - 113 - // Main lexicon first (so it shows in the playground) 114 - if (mainLexicon) { 115 - const contentWithoutImport = mainLexicon.content.replace( 116 - /^import "@typelex\/emitter";\s*\n/, 117 - "", 118 - ); 119 - bundled += `// ${mainLexicon.file}\n${contentWithoutImport}\n`; 120 - } 121 - 122 - // Then dependencies 123 - for (const ns of deps) { 124 - const lexicon = lexicons.get(ns); 125 - if (!lexicon) continue; 126 - 127 - const contentWithoutImport = lexicon.content.replace( 128 - /^import "@typelex\/emitter";\s*\n/, 129 - "", 130 - ); 131 - bundled += `// ${lexicon.file}\n${contentWithoutImport}\n`; 132 - } 133 - 134 - return bundled; 135 } 136 137 - // Export for build script 138 - export { lexicons, bundleLexicon }; 139 140 - console.log(`Loaded ${lexicons.size} lexicons for bundling`);
··· 6 const __dirname = dirname(fileURLToPath(import.meta.url)); 7 8 // Get all tsp files 9 + function getAllFiles(dir, baseDir = dir) { 10 const files = []; 11 const entries = readdirSync(dir); 12 ··· 15 const stat = statSync(fullPath); 16 17 if (stat.isDirectory()) { 18 + files.push(...getAllFiles(fullPath, baseDir)); 19 } else if (entry.endsWith(".tsp")) { 20 files.push(relative(baseDir, fullPath)); 21 } ··· 24 return files.sort(); 25 } 26 27 + const integrationDir = join(__dirname, "../../emitter/test/integration"); 28 29 + // Get all test suite directories 30 + const testSuites = readdirSync(integrationDir).filter((name) => { 31 + const fullPath = join(integrationDir, name); 32 + return statSync(fullPath).isDirectory() && !name.startsWith("."); 33 + }); 34 35 + // Load all lexicons from test suites 36 + const lexicons = new Map(); // namespace -> { file, content, suite } 37 38 + for (const suite of testSuites) { 39 + const inputDir = join(integrationDir, suite, "input"); 40 + const inputFiles = getAllFiles(inputDir).filter((f) => f.endsWith(".tsp")); 41 42 + for (const file of inputFiles) { 43 + const fullPath = join(inputDir, file); 44 + const content = readFileSync(fullPath, "utf-8"); 45 + const namespace = file.replace(/\.tsp$/, "").replace(/\//g, "."); 46 47 + lexicons.set(namespace, { file, content, suite, fullPath }); 48 } 49 } 50 51 + // Build samples list for playground 52 + const samplesList = {}; 53 + for (const [namespace, lexicon] of lexicons) { 54 + samplesList[namespace] = { 55 + filename: relative(join(__dirname, ".."), lexicon.fullPath), 56 + preferredEmitter: "@typelex/emitter", 57 + }; 58 } 59 60 + export { lexicons }; 61 + export default samplesList; 62 63 + console.log(`Loaded ${lexicons.size} lexicons`);
+1 -1
packages/playground/vite.config.ts
··· 1 import { definePlaygroundViteConfig } from "@typespec/playground/vite"; 2 import { defineConfig } from "vite"; 3 - import samples from "./samples/dist/samples.js"; 4 5 const playgroundConfig = definePlaygroundViteConfig({ 6 defaultEmitter: "@typelex/emitter",
··· 1 import { definePlaygroundViteConfig } from "@typespec/playground/vite"; 2 import { defineConfig } from "vite"; 3 + import samples from "./samples/index.js"; 4 5 const playgroundConfig = definePlaygroundViteConfig({ 6 defaultEmitter: "@typelex/emitter",
+10 -11
packages/website/src/pages/index.astro
··· 1 --- 2 - import Playground from '../components/Playground'; 3 import { highlightCode } from '../utils/shiki'; 4 import { compileToJson } from '../utils/compile'; 5 import { createPlaygroundUrl } from '../utils/playground-url'; ··· 236 <nav class="hero-actions"> 237 <a href="#install" class="install-cta">Try It</a> 238 <a href="https://tangled.org/@danabra.mov/typelex/blob/main/DOCS.md" target="_blank" rel="noopener noreferrer" class="star-btn"> 239 - Read Documentation 240 </a> 241 </nav> 242 </header> ··· 277 <h2>Install</h2> 278 <div class="install-grid"> 279 <div class="install-notice"> 280 - <p class="notice-text">This is an early-stage experiment. It's probably buggy as hell.</p> 281 </div> 282 <div class="install-step playground-step"> 283 <div class="step-number">0</div> ··· 498 499 header { 500 text-align: center; 501 - padding: 3rem 1rem 2rem; 502 margin: 0 auto; 503 } 504 ··· 649 650 .hero-actions { 651 display: flex; 652 - flex-direction: column; 653 - gap: 1rem; 654 margin-top: 2.5rem; 655 align-items: center; 656 } 657 658 @media (min-width: 640px) { 659 .hero-actions { 660 - flex-direction: row; 661 - justify-content: center; 662 gap: 1rem; 663 } 664 } ··· 797 798 .install-notice { 799 padding: 1rem 1.5rem; 800 - background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%); 801 - border: 2px solid #ef4444; 802 border-radius: 10px; 803 text-align: left; 804 } ··· 808 font-size: 1.0625rem; 809 line-height: 1.5; 810 font-weight: 700; 811 - color: #991b1b; 812 } 813 814 .install-grid {
··· 1 --- 2 import { highlightCode } from '../utils/shiki'; 3 import { compileToJson } from '../utils/compile'; 4 import { createPlaygroundUrl } from '../utils/playground-url'; ··· 235 <nav class="hero-actions"> 236 <a href="#install" class="install-cta">Try It</a> 237 <a href="https://tangled.org/@danabra.mov/typelex/blob/main/DOCS.md" target="_blank" rel="noopener noreferrer" class="star-btn"> 238 + Read Docs 239 </a> 240 </nav> 241 </header> ··· 276 <h2>Install</h2> 277 <div class="install-grid"> 278 <div class="install-notice"> 279 + <p class="notice-text">This is an early-stage experiment. Design and syntax may change.</p> 280 </div> 281 <div class="install-step playground-step"> 282 <div class="step-number">0</div> ··· 497 498 header { 499 text-align: center; 500 + padding: 3rem 1rem 0.5rem; 501 margin: 0 auto; 502 } 503 ··· 648 649 .hero-actions { 650 display: flex; 651 + flex-direction: row; 652 + flex-wrap: wrap; 653 + gap: 0.75rem; 654 margin-top: 2.5rem; 655 align-items: center; 656 + justify-content: center; 657 } 658 659 @media (min-width: 640px) { 660 .hero-actions { 661 gap: 1rem; 662 } 663 } ··· 796 797 .install-notice { 798 padding: 1rem 1.5rem; 799 + background: linear-gradient(135deg, #fefce8 0%, #fef3c7 100%); 800 + border: 2px solid #f59e0b; 801 border-radius: 10px; 802 text-align: left; 803 } ··· 807 font-size: 1.0625rem; 808 line-height: 1.5; 809 font-weight: 700; 810 + color: #92400e; 811 } 812 813 .install-grid {
+46 -3
pnpm-lock.yaml
··· 12 specifier: ^5.0.0 13 version: 5.9.3 14 15 packages/emitter: 16 dependencies: 17 '@typespec/compiler': ··· 48 '@atproto/xrpc-server': 49 specifier: ^0.9.5 50 version: 0.9.5 51 '@typelex/emitter': 52 specifier: workspace:* 53 version: link:../emitter 54 - '@typespec/compiler': 55 - specifier: ^1.4.0 56 - version: 1.4.0(@types/node@20.19.19) 57 devDependencies: 58 typescript: 59 specifier: ^5.0.0 ··· 1645 '@ts-morph/common@0.25.0': 1646 resolution: {integrity: sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==} 1647 1648 '@types/babel__core@7.20.5': 1649 resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 1650 ··· 1705 1706 '@types/unist@3.0.3': 1707 resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} 1708 1709 '@typespec/asset-emitter@0.74.0': 1710 resolution: {integrity: sha512-DWIdlSNhRgBeZ8exfqubfUn0H6mRg4gr0s7zLTdBMUEDHL3Yh0ljnRPkd8AXTZhoW3maTFT69loWTrqx09T5oQ==} ··· 7411 path-browserify: 1.0.1 7412 tinyglobby: 0.2.15 7413 7414 '@types/babel__core@7.20.5': 7415 dependencies: 7416 '@babel/parser': 7.28.4 ··· 7482 csstype: 3.1.3 7483 7484 '@types/unist@3.0.3': {} 7485 7486 '@typespec/asset-emitter@0.74.0(@typespec/compiler@1.4.0(@types/node@20.19.19))': 7487 dependencies:
··· 12 specifier: ^5.0.0 13 version: 5.9.3 14 15 + packages/cli: 16 + dependencies: 17 + '@typelex/emitter': 18 + specifier: ^0.2.0 19 + version: 0.2.0(@typespec/compiler@1.4.0(@types/node@20.19.19)) 20 + '@typespec/compiler': 21 + specifier: ^1.4.0 22 + version: 1.4.0(@types/node@20.19.19) 23 + yargs: 24 + specifier: ^18.0.0 25 + version: 18.0.0 26 + devDependencies: 27 + '@types/node': 28 + specifier: ^20.0.0 29 + version: 20.19.19 30 + '@types/yargs': 31 + specifier: ^17.0.33 32 + version: 17.0.33 33 + typescript: 34 + specifier: ^5.0.0 35 + version: 5.9.3 36 + 37 packages/emitter: 38 dependencies: 39 '@typespec/compiler': ··· 70 '@atproto/xrpc-server': 71 specifier: ^0.9.5 72 version: 0.9.5 73 + '@typelex/cli': 74 + specifier: workspace:* 75 + version: link:../cli 76 '@typelex/emitter': 77 specifier: workspace:* 78 version: link:../emitter 79 devDependencies: 80 typescript: 81 specifier: ^5.0.0 ··· 1667 '@ts-morph/common@0.25.0': 1668 resolution: {integrity: sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==} 1669 1670 + '@typelex/emitter@0.2.0': 1671 + resolution: {integrity: sha512-4Iw6VAnd9nCFGOkJcu9utWdmu9ZyPeAb1QX/B7KerGBmfc2FuIDqgZZ/mZ6c56atcZd62pb2oYF/3RgSFhEsoQ==} 1672 + peerDependencies: 1673 + '@typespec/compiler': ^1.4.0 1674 + 1675 '@types/babel__core@7.20.5': 1676 resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 1677 ··· 1732 1733 '@types/unist@3.0.3': 1734 resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} 1735 + 1736 + '@types/yargs-parser@21.0.3': 1737 + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} 1738 + 1739 + '@types/yargs@17.0.33': 1740 + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} 1741 1742 '@typespec/asset-emitter@0.74.0': 1743 resolution: {integrity: sha512-DWIdlSNhRgBeZ8exfqubfUn0H6mRg4gr0s7zLTdBMUEDHL3Yh0ljnRPkd8AXTZhoW3maTFT69loWTrqx09T5oQ==} ··· 7444 path-browserify: 1.0.1 7445 tinyglobby: 0.2.15 7446 7447 + '@typelex/emitter@0.2.0(@typespec/compiler@1.4.0(@types/node@20.19.19))': 7448 + dependencies: 7449 + '@typespec/compiler': 1.4.0(@types/node@20.19.19) 7450 + 7451 '@types/babel__core@7.20.5': 7452 dependencies: 7453 '@babel/parser': 7.28.4 ··· 7519 csstype: 3.1.3 7520 7521 '@types/unist@3.0.3': {} 7522 + 7523 + '@types/yargs-parser@21.0.3': {} 7524 + 7525 + '@types/yargs@17.0.33': 7526 + dependencies: 7527 + '@types/yargs-parser': 21.0.3 7528 7529 '@typespec/asset-emitter@0.74.0(@typespec/compiler@1.4.0(@types/node@20.19.19))': 7530 dependencies: