An experimental TypeSpec syntax for Lexicon

0.3.0

+36 -71
+5
CHANGELOG.md
··· 1 ### 0.2.0 2 3 - Add `@external` support
··· 1 + ### 0.3.0 2 + 3 + - New package `@typelex/cli` 4 + - See new recommended workflow on https://typelex.org/#install 5 + 6 ### 0.2.0 7 8 - Add `@external` support
+1 -30
DOCS.md
··· 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"; 265 - import "../atproto-stubs.tsp"; 266 - 267 - namespace app.bsky.actor.profile { 268 - model Main { 269 - labels?: (com.atproto.label.defs.SelfLabels | unknown); 270 - } 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
··· 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 + Starting with 0.3.0, typelex will automatically generate a `typelex/externals.tsp` file based on the JSON files in your `lexicons/` folder, and enforce that it's imported into your `typelex/main.tsp` entry point. However, this will *not* include Lexicons from your app's namespace, but only external ones. 262 263 You'll want to ensure the real JSON for external Lexicons is available before running codegen. 264
+2 -2
packages/cli/package.json
··· 1 { 2 "name": "@typelex/cli", 3 - "version": "0.2.15", 4 "main": "dist/index.js", 5 "type": "module", 6 "bin": { ··· 40 "@typelex/emitter": "workspace:*" 41 }, 42 "peerDependencies": { 43 - "@typelex/emitter": "^0.2.0" 44 } 45 }
··· 1 { 2 "name": "@typelex/cli", 3 + "version": "0.3.0", 4 "main": "dist/index.js", 5 "type": "module", 6 "bin": { ··· 40 "@typelex/emitter": "workspace:*" 41 }, 42 "peerDependencies": { 43 + "@typelex/emitter": "^0.3.0" 44 } 45 }
+6 -5
packages/cli/src/commands/init.ts
··· 58 59 return new Promise((resolve) => { 60 rl.question( 61 - `Enter your app's root namespace (e.g. ${pc.cyan("com.example.*")}): `, 62 (answer) => { 63 rl.close(); 64 resolve(answer.trim()); ··· 90 return initSetup(); 91 } 92 93 - console.log(`Adding ${gradientText("typelex")}...\n`); 94 95 // Detect package manager 96 let packageManager = "npm"; ··· 240 241 // Inform about external lexicons 242 console.log( 243 - `\nLexicons other than ${pc.cyan(namespace)} will be considered external.`, 244 ); 245 console.log( 246 - `Put them into the ${pc.cyan(displayLexiconsPath)} folder as JSON.\n`, 247 ); 248 249 // Create typelex directory ··· 321 console.log(`\n${pc.green("✓")} ${pc.bold("All set!")}`); 322 console.log(`\n${pc.bold("Next steps:")}`); 323 console.log( 324 - ` ${pc.dim("1.")} Edit ${pc.cyan("typelex/main.tsp")} to define your lexicons`, 325 ); 326 console.log( 327 ` ${pc.dim("2.")} Keep putting external lexicons into ${pc.cyan(displayLexiconsPath)}`,
··· 58 59 return new Promise((resolve) => { 60 rl.question( 61 + `Which Lexicons do you want to write in typelex (e.g. ${pc.cyan("com.example.*")})? `, 62 (answer) => { 63 rl.close(); 64 resolve(answer.trim()); ··· 90 return initSetup(); 91 } 92 93 + console.log(gradientText("Adding typelex...") + "\n"); 94 95 // Detect package manager 96 let packageManager = "npm"; ··· 240 241 // Inform about external lexicons 242 console.log( 243 + `\nLexicons for ${pc.cyan(namespace)} will now be managed by typelex.`, 244 ); 245 + console.log(`You can begin writing them in ${pc.cyan("typelex/main.tsp")}.`); 246 console.log( 247 + `Any external lexicons should remain in ${pc.cyan(displayLexiconsPath)}.\n`, 248 ); 249 250 // Create typelex directory ··· 322 console.log(`\n${pc.green("✓")} ${pc.bold("All set!")}`); 323 console.log(`\n${pc.bold("Next steps:")}`); 324 console.log( 325 + ` ${pc.dim("1.")} Edit ${pc.cyan("typelex/main.tsp")} to define the ${pc.cyan(namespace)} lexicons`, 326 ); 327 console.log( 328 ` ${pc.dim("2.")} Keep putting external lexicons into ${pc.cyan(displayLexiconsPath)}`,
+1 -1
packages/emitter/package.json
··· 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",
··· 1 { 2 "name": "@typelex/emitter", 3 + "version": "0.3.0", 4 "description": "TypeSpec emitter for ATProto Lexicon definitions", 5 "main": "dist/index.js", 6 "type": "module",
+17
packages/website/src/layouts/BaseLayout.astro
··· 50 51 <slot /> 52 53 {transparentNav && ( 54 <script> 55 const nav = document.querySelector('.top-nav');
··· 50 51 <slot /> 52 53 + <script> 54 + // Smooth scroll to top when clicking logo 55 + document.addEventListener('DOMContentLoaded', () => { 56 + const logo = document.querySelector('.logo'); 57 + if (logo) { 58 + logo.addEventListener('click', (e) => { 59 + // Allow Ctrl/Cmd+click to open in new tab 60 + if (e.ctrlKey || e.metaKey || e.shiftKey) { 61 + return; 62 + } 63 + e.preventDefault(); 64 + window.scrollTo({ top: 0, behavior: 'smooth' }); 65 + }); 66 + } 67 + }); 68 + </script> 69 + 70 {transparentNav && ( 71 <script> 72 const nav = document.querySelector('.top-nav');
+4 -33
packages/website/src/pages/index.astro
··· 179 <div class="step-content"> 180 <h3>Add typelex to your app</h3> 181 <figure class="install-box" set:html={await highlightCode('npx @typelex/cli init', 'bash')} /> 182 - <p class="step-description">This will add a few things to your <code>package.json</code>, and create a <code>typelex/</code> folder.</p> 183 </div> 184 </div> 185 ··· 187 <div class="step-number">2</div> 188 <div class="step-content"> 189 <h3>Write your lexicons in <code>typelex/main.tsp</code></h3> 190 - <figure class="install-box install-box-with-link"> 191 - <a href={createPlaygroundUrl(`import "@typelex/emitter"; 192 - import "./externals.tsp"; 193 - 194 - namespace com.myapp.example.profile { 195 - /** My profile. */ 196 - @rec("literal:self") 197 - model Main { 198 - /** Free-form profile description.*/ 199 - @maxGraphemes(256) 200 - description?: string; 201 - } 202 - }`)} target="_blank" rel="noopener noreferrer" class="install-playground-link" aria-label="Open in playground"> 203 - <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 204 - <path d="M6.5 3.5C6.5 3.22386 6.72386 3 7 3H13C13.2761 3 13.5 3.22386 13.5 3.5V9.5C13.5 9.77614 13.2761 10 13 10C12.7239 10 12.5 9.77614 12.5 9.5V4.70711L6.85355 10.3536C6.65829 10.5488 6.34171 10.5488 6.14645 10.3536C5.95118 10.1583 5.95118 9.84171 6.14645 9.64645L11.7929 4H7C6.72386 4 6.5 3.77614 6.5 3.5Z" fill="currentColor"/> 205 - <path d="M3 5.5C3 4.67157 3.67157 4 4.5 4H5C5.27614 4 5.5 4.22386 5.5 4.5C5.5 4.77614 5.27614 5 5 5H4.5C4.22386 5 4 5.22386 4 5.5V11.5C4 11.7761 4.22386 12 4.5 12H10.5C10.7761 12 11 11.7761 11 11.5V11C11 10.7239 11.2239 10.5 11.5 10.5C11.7761 10.5 12 10.7239 12 11V11.5C12 12.3284 11.3284 13 10.5 13H4.5C3.67157 13 3 12.3284 3 11.5V5.5Z" fill="currentColor"/> 206 - </svg> 207 - </a> 208 - <div set:html={await highlightCode(`import "@typelex/emitter"; 209 - import "./externals.tsp"; 210 - 211 - namespace com.myapp.example.profile { 212 - /** My profile. */ 213 - @rec("literal:self") 214 - model Main { 215 - /** Free-form profile description.*/ 216 - @maxGraphemes(256) 217 - description?: string; 218 - } 219 - }`, 'typespec')} /> 220 - </figure> 221 </div> 222 - <p class="step-description">Your app's lexicons go here. They may reference any external ones from <code>lexicons/</code>. 223 </div> 224 225 <div class="install-step"> ··· 622 .install-section { 623 margin: 0; 624 padding: 0; 625 } 626 627 .install-section h2 {
··· 179 <div class="step-content"> 180 <h3>Add typelex to your app</h3> 181 <figure class="install-box" set:html={await highlightCode('npx @typelex/cli init', 'bash')} /> 182 + <p class="step-description">This will add a few things to your <code>package.json</code> and create a <code>typelex/</code> folder.</p> 183 </div> 184 </div> 185 ··· 187 <div class="step-number">2</div> 188 <div class="step-content"> 189 <h3>Write your lexicons in <code>typelex/main.tsp</code></h3> 190 + <figure class="install-box" set:html={await highlightCode(installCode, 'typespec')} /> 191 + <p class="step-description">Your app's lexicons go here. They may reference any external ones from <code>lexicons/</code>.</p> 192 </div> 193 </div> 194 195 <div class="install-step"> ··· 592 .install-section { 593 margin: 0; 594 padding: 0; 595 + scroll-margin-top: 5rem; 596 } 597 598 .install-section h2 {