tangled
alpha
login
or
join now
danabra.mov
/
typelex
56
fork
atom
An experimental TypeSpec syntax for Lexicon
56
fork
atom
overview
issues
1
pulls
2
pipelines
0.2.15
danabra.mov
5 months ago
82424d08
c24bc9bd
+42
-129
3 changed files
expand all
collapse all
unified
split
packages
cli
package.json
website
src
components
ComparisonBlock.astro
pages
index.astro
+1
-1
packages/cli/package.json
···
1
{
2
"name": "@typelex/cli",
3
-
"version": "0.2.14",
4
"main": "dist/index.js",
5
"type": "module",
6
"bin": {
···
1
{
2
"name": "@typelex/cli",
3
+
"version": "0.2.15",
4
"main": "dist/index.js",
5
"type": "module",
6
"bin": {
+15
-10
packages/website/src/components/ComparisonBlock.astro
···
9
10
interface Props {
11
code: string;
0
12
}
13
14
-
const { code } = Astro.props;
15
16
// Create temporary file for compilation
17
const tmpDir = mkdtempSync(join(tmpdir(), 'typelex-'));
···
23
24
try {
25
lexiconJson = await compileToJson(tmpFile);
26
-
lexicon = stringify(JSON.parse(lexiconJson), { maxLength: 80 });
27
} finally {
28
rmSync(tmpDir, { recursive: true, force: true });
29
}
···
31
const typelexHtml = await highlightCode(code, 'typespec');
32
const lexiconHtml = await highlightCode(lexicon, 'json');
33
const playgroundUrl = createPlaygroundUrl(code);
0
0
0
0
34
---
35
36
-
<div class="comparison">
37
<div class="comparison-content">
38
-
<div class="code-panel">
39
-
<p class="code-header">
40
Typelex
41
<a href={playgroundUrl} target="_blank" rel="noopener noreferrer" class="code-playground-link" aria-label="Open in playground">
42
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
···
45
</svg>
46
</a>
47
</p>
48
-
<div class="code-block" set:html={typelexHtml} />
49
</div>
50
-
<div class="code-panel">
51
-
<p class="code-header">
52
Lexicon
53
</p>
54
-
<div class="code-block" set:html={lexiconHtml} />
55
</div>
56
</div>
57
-
</div>
···
9
10
interface Props {
11
code: string;
12
+
hero?: boolean;
13
}
14
15
+
const { code, hero = false } = Astro.props;
16
17
// Create temporary file for compilation
18
const tmpDir = mkdtempSync(join(tmpdir(), 'typelex-'));
···
24
25
try {
26
lexiconJson = await compileToJson(tmpFile);
27
+
lexicon = stringify(JSON.parse(lexiconJson), { maxLength: hero ? 50 : 80 });
28
} finally {
29
rmSync(tmpDir, { recursive: true, force: true });
30
}
···
32
const typelexHtml = await highlightCode(code, 'typespec');
33
const lexiconHtml = await highlightCode(lexicon, 'json');
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';
39
---
40
41
+
<figure class:list={[hero ? 'hero-comparison' : 'comparison']}>
42
<div class="comparison-content">
43
+
<div class={panelClass}>
44
+
<p class={headerClass}>
45
Typelex
46
<a href={playgroundUrl} target="_blank" rel="noopener noreferrer" class="code-playground-link" aria-label="Open in playground">
47
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
···
50
</svg>
51
</a>
52
</p>
53
+
<div class={blockClass} set:html={typelexHtml} />
54
</div>
55
+
<div class={panelClass}>
56
+
<p class={headerClass}>
57
Lexicon
58
</p>
59
+
<div class={blockClass} set:html={lexiconHtml} />
60
</div>
61
</div>
62
+
</figure>
+26
-118
packages/website/src/pages/index.astro
···
1
---
2
import BaseLayout from '../layouts/BaseLayout.astro';
0
3
import { highlightCode } from '../utils/shiki';
4
-
import { compileToJson } from '../utils/compile';
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
11
// Define examples inline
12
const examples = [
13
{
14
title: "Records and properties",
15
-
typelex: `import "@typelex/emitter";
16
17
namespace fm.teal.alpha.feed.play {
18
@rec("tid")
19
model Main {
20
@maxItems(10)
21
artistNames?: string[];
22
-
23
@required
24
@minLength(1)
25
@maxLength(256)
···
32
},
33
{
34
title: "Refs and unions",
35
-
typelex: `import "@typelex/emitter";
36
37
namespace app.bsky.feed.post {
38
@rec("tid")
···
67
},
68
{
69
title: "Queries and params",
70
-
typelex: `import "@typelex/emitter";
71
72
namespace com.atproto.repo.listRecords {
73
@query
···
100
},
101
];
102
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";
140
141
namespace app.bsky.actor.profile {
142
@rec("self")
···
149
@maxGraphemes(256)
150
description?: string;
151
}
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";
160
161
-
namespace app.bsky.actor.profile {
162
-
@rec("self")
163
-
model Main {
164
-
@maxLength(64)
165
-
@maxGraphemes(64)
166
-
displayName?: string;
167
168
-
@maxLength(256)
0
0
0
0
169
@maxGraphemes(256)
170
description?: string;
171
}
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>
206
207
<p class="hero-description">
208
Typelex lets you write AT <a target="_blank" href="https://atproto.com/specs/lexicon">Lexicons</a> in a more readable syntax. <br />
···
219
220
<hr class="separator" />
221
222
-
{highlighted.map(({ title, typelexHtml, lexiconHtml, playgroundUrl }) => (
223
<section>
224
<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>
247
</section>
248
))}
249
···
1
---
2
import BaseLayout from '../layouts/BaseLayout.astro';
3
+
import ComparisonBlock from '../components/ComparisonBlock.astro';
4
import { highlightCode } from '../utils/shiki';
0
5
import { createPlaygroundUrl } from '../utils/playground-url';
0
0
0
0
6
7
// Define examples inline
8
const examples = [
9
{
10
title: "Records and properties",
11
+
code: `import "@typelex/emitter";
12
13
namespace fm.teal.alpha.feed.play {
14
@rec("tid")
15
model Main {
16
@maxItems(10)
17
artistNames?: string[];
18
+
19
@required
20
@minLength(1)
21
@maxLength(256)
···
28
},
29
{
30
title: "Refs and unions",
31
+
code: `import "@typelex/emitter";
32
33
namespace app.bsky.feed.post {
34
@rec("tid")
···
63
},
64
{
65
title: "Queries and params",
66
+
code: `import "@typelex/emitter";
67
68
namespace com.atproto.repo.listRecords {
69
@query
···
96
},
97
];
98
99
+
const heroCode = `import "@typelex/emitter";
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
100
101
namespace app.bsky.actor.profile {
102
@rec("self")
···
109
@maxGraphemes(256)
110
description?: string;
111
}
112
+
}`;
0
0
0
0
0
0
0
113
114
+
const installCode = `import "@typelex/emitter";
115
+
import "./externals.tsp";
0
0
0
0
116
117
+
namespace com.myapp.example.profile {
118
+
/** My profile. */
119
+
@rec("literal:self")
120
+
model Main {
121
+
/** Free-form profile description.*/
122
@maxGraphemes(256)
123
description?: string;
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
+
<ComparisonBlock code={heroCode} hero={true} />
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
135
136
<p class="hero-description">
137
Typelex lets you write AT <a target="_blank" href="https://atproto.com/specs/lexicon">Lexicons</a> in a more readable syntax. <br />
···
148
149
<hr class="separator" />
150
151
+
{examples.map(({ title, code }) => (
152
<section>
153
<h2>{title}</h2>
154
+
<ComparisonBlock code={code} />
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
155
</section>
156
))}
157