An experimental TypeSpec syntax for Lexicon

0.2.15

+42 -129
+1 -1
packages/cli/package.json
··· 1 1 { 2 2 "name": "@typelex/cli", 3 - "version": "0.2.14", 3 + "version": "0.2.15", 4 4 "main": "dist/index.js", 5 5 "type": "module", 6 6 "bin": {
+15 -10
packages/website/src/components/ComparisonBlock.astro
··· 9 9 10 10 interface Props { 11 11 code: string; 12 + hero?: boolean; 12 13 } 13 14 14 - const { code } = Astro.props; 15 + const { code, hero = false } = Astro.props; 15 16 16 17 // Create temporary file for compilation 17 18 const tmpDir = mkdtempSync(join(tmpdir(), 'typelex-')); ··· 23 24 24 25 try { 25 26 lexiconJson = await compileToJson(tmpFile); 26 - lexicon = stringify(JSON.parse(lexiconJson), { maxLength: 80 }); 27 + lexicon = stringify(JSON.parse(lexiconJson), { maxLength: hero ? 50 : 80 }); 27 28 } finally { 28 29 rmSync(tmpDir, { recursive: true, force: true }); 29 30 } ··· 31 32 const typelexHtml = await highlightCode(code, 'typespec'); 32 33 const lexiconHtml = await highlightCode(lexicon, 'json'); 33 34 const playgroundUrl = createPlaygroundUrl(code); 35 + 36 + const panelClass = hero ? 'hero-panel' : 'code-panel'; 37 + const headerClass = hero ? 'hero-header' : 'code-header'; 38 + const blockClass = hero ? 'hero-code' : 'code-block'; 34 39 --- 35 40 36 - <div class="comparison"> 41 + <figure class:list={[hero ? 'hero-comparison' : 'comparison']}> 37 42 <div class="comparison-content"> 38 - <div class="code-panel"> 39 - <p class="code-header"> 43 + <div class={panelClass}> 44 + <p class={headerClass}> 40 45 Typelex 41 46 <a href={playgroundUrl} target="_blank" rel="noopener noreferrer" class="code-playground-link" aria-label="Open in playground"> 42 47 <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> ··· 45 50 </svg> 46 51 </a> 47 52 </p> 48 - <div class="code-block" set:html={typelexHtml} /> 53 + <div class={blockClass} set:html={typelexHtml} /> 49 54 </div> 50 - <div class="code-panel"> 51 - <p class="code-header"> 55 + <div class={panelClass}> 56 + <p class={headerClass}> 52 57 Lexicon 53 58 </p> 54 - <div class="code-block" set:html={lexiconHtml} /> 59 + <div class={blockClass} set:html={lexiconHtml} /> 55 60 </div> 56 61 </div> 57 - </div> 62 + </figure>
+26 -118
packages/website/src/pages/index.astro
··· 1 1 --- 2 2 import BaseLayout from '../layouts/BaseLayout.astro'; 3 + import ComparisonBlock from '../components/ComparisonBlock.astro'; 3 4 import { highlightCode } from '../utils/shiki'; 4 - import { compileToJson } from '../utils/compile'; 5 5 import { createPlaygroundUrl } from '../utils/playground-url'; 6 - import stringify from 'json-stringify-pretty-compact'; 7 - import { mkdtempSync, writeFileSync, rmSync } from 'fs'; 8 - import { join } from 'path'; 9 - import { tmpdir } from 'os'; 10 6 11 7 // Define examples inline 12 8 const examples = [ 13 9 { 14 10 title: "Records and properties", 15 - typelex: `import "@typelex/emitter"; 11 + code: `import "@typelex/emitter"; 16 12 17 13 namespace fm.teal.alpha.feed.play { 18 14 @rec("tid") 19 15 model Main { 20 16 @maxItems(10) 21 17 artistNames?: string[]; 22 - 18 + 23 19 @required 24 20 @minLength(1) 25 21 @maxLength(256) ··· 32 28 }, 33 29 { 34 30 title: "Refs and unions", 35 - typelex: `import "@typelex/emitter"; 31 + code: `import "@typelex/emitter"; 36 32 37 33 namespace app.bsky.feed.post { 38 34 @rec("tid") ··· 67 63 }, 68 64 { 69 65 title: "Queries and params", 70 - typelex: `import "@typelex/emitter"; 66 + code: `import "@typelex/emitter"; 71 67 72 68 namespace com.atproto.repo.listRecords { 73 69 @query ··· 100 96 }, 101 97 ]; 102 98 103 - // Compile examples 104 - const highlighted = await Promise.all( 105 - examples.map(async (ex) => { 106 - // Create temporary file for compilation 107 - const tmpDir = mkdtempSync(join(tmpdir(), 'typelex-')); 108 - const tmpFile = join(tmpDir, 'example.tsp'); 109 - writeFileSync(tmpFile, ex.typelex); 110 - 111 - try { 112 - const lexiconJson = await compileToJson(tmpFile); 113 - const lexicon = stringify(JSON.parse(lexiconJson), { maxLength: 80 }); 114 - 115 - return { 116 - ...ex, 117 - typelexHtml: await highlightCode(ex.typelex, 'typespec'), 118 - lexiconHtml: await highlightCode(lexicon, 'json'), 119 - playgroundUrl: createPlaygroundUrl(ex.typelex), 120 - }; 121 - } finally { 122 - rmSync(tmpDir, { recursive: true, force: true }); 123 - } 124 - }) 125 - ); 126 - --- 127 - 128 - <BaseLayout title="typelex – An experimental TypeSpec syntax for Lexicon" transparentNav={true}> 129 - <main class="container"> 130 - <header> 131 - <h1>typelex</h1> 132 - <p class="tagline">An experimental <a href="https://typespec.io" target="_blank" rel="noopener noreferrer">TypeSpec</a> syntax for <a href="https://atproto.com/specs/lexicon" target="_blank" rel="noopener noreferrer">Lexicon</a></p> 133 - 134 - <figure class="hero-comparison"> 135 - <div class="comparison-content"> 136 - <div class="hero-panel"> 137 - <p class="hero-header"> 138 - Typelex 139 - <a href={createPlaygroundUrl(`import "@typelex/emitter"; 99 + const heroCode = `import "@typelex/emitter"; 140 100 141 101 namespace app.bsky.actor.profile { 142 102 @rec("self") ··· 149 109 @maxGraphemes(256) 150 110 description?: string; 151 111 } 152 - }`)} target="_blank" rel="noopener noreferrer" class="code-playground-link" aria-label="Open in playground"> 153 - <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 154 - <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"/> 155 - <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"/> 156 - </svg> 157 - </a> 158 - </p> 159 - <div class="hero-code" set:html={await highlightCode(`import "@typelex/emitter"; 112 + }`; 160 113 161 - namespace app.bsky.actor.profile { 162 - @rec("self") 163 - model Main { 164 - @maxLength(64) 165 - @maxGraphemes(64) 166 - displayName?: string; 114 + const installCode = `import "@typelex/emitter"; 115 + import "./externals.tsp"; 167 116 168 - @maxLength(256) 117 + namespace com.myapp.example.profile { 118 + /** My profile. */ 119 + @rec("literal:self") 120 + model Main { 121 + /** Free-form profile description.*/ 169 122 @maxGraphemes(256) 170 123 description?: string; 171 124 } 172 - }`, 'typespec')} /> 173 - </div> 174 - <div class="hero-panel"> 175 - <p class="hero-header"> 176 - Lexicon 177 - </p> 178 - <div class="hero-code" set:html={await highlightCode(stringify({ 179 - "lexicon": 1, 180 - "id": "app.bsky.actor.profile", 181 - "defs": { 182 - "main": { 183 - "type": "record", 184 - "key": "self", 185 - "record": { 186 - "type": "object", 187 - "properties": { 188 - "displayName": { 189 - "type": "string", 190 - "maxLength": 64, 191 - "maxGraphemes": 64 192 - }, 193 - "description": { 194 - "type": "string", 195 - "maxLength": 256, 196 - "maxGraphemes": 256 197 - } 198 - } 199 - } 200 - } 201 - } 202 - }, { maxLength: 50 }), 'json')} /> 203 - </div> 204 - </div> 205 - </figure> 125 + }`; 126 + --- 127 + 128 + <BaseLayout title="typelex – An experimental TypeSpec syntax for Lexicon" transparentNav={true}> 129 + <main class="container"> 130 + <header> 131 + <h1>typelex</h1> 132 + <p class="tagline">An experimental <a href="https://typespec.io" target="_blank" rel="noopener noreferrer">TypeSpec</a> syntax for <a href="https://atproto.com/specs/lexicon" target="_blank" rel="noopener noreferrer">Lexicon</a></p> 133 + 134 + <ComparisonBlock code={heroCode} hero={true} /> 206 135 207 136 <p class="hero-description"> 208 137 Typelex lets you write AT <a target="_blank" href="https://atproto.com/specs/lexicon">Lexicons</a> in a more readable syntax. <br /> ··· 219 148 220 149 <hr class="separator" /> 221 150 222 - {highlighted.map(({ title, typelexHtml, lexiconHtml, playgroundUrl }) => ( 151 + {examples.map(({ title, code }) => ( 223 152 <section> 224 153 <h2>{title}</h2> 225 - <figure class="comparison"> 226 - <div class="comparison-content"> 227 - <div class="code-panel"> 228 - <p class="code-header"> 229 - Typelex 230 - <a href={playgroundUrl} target="_blank" rel="noopener noreferrer" class="code-playground-link" aria-label="Open in playground"> 231 - <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 232 - <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"/> 233 - <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"/> 234 - </svg> 235 - </a> 236 - </p> 237 - <div class="code-block" set:html={typelexHtml} /> 238 - </div> 239 - <div class="code-panel"> 240 - <p class="code-header"> 241 - Lexicon 242 - </p> 243 - <div class="code-block" set:html={lexiconHtml} /> 244 - </div> 245 - </div> 246 - </figure> 154 + <ComparisonBlock code={code} /> 247 155 </section> 248 156 ))} 249 157