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
wip
danabra.mov
5 months ago
f2132988
22b8620a
+40
-187
3 changed files
expand all
collapse all
unified
split
packages
emitter
package.json
src
emitter.ts
types.ts
+1
packages/emitter/package.json
···
36
36
"@typespec/compiler": "^1.4.0"
37
37
},
38
38
"devDependencies": {
39
39
+
"@atproto/lexicon": "^0.5.1",
39
40
"@types/node": "^20.0.0",
40
41
"@typespec/http": "^1.4.0",
41
42
"@typespec/openapi": "^1.4.0",
+39
-34
packages/emitter/src/emitter.ts
···
6
6
Scalar,
7
7
Union,
8
8
Namespace,
9
9
-
Operation,
10
9
StringLiteral,
11
10
NumericLiteral,
12
11
BooleanLiteral,
···
26
25
} from "@typespec/compiler";
27
26
import { join, dirname } from "path";
28
27
import type {
29
29
-
LexiconDocument,
30
30
-
LexiconDefinition,
31
31
-
LexiconObject,
32
32
-
LexiconPrimitive,
33
33
-
LexiconArray,
34
34
-
LexiconRef,
35
35
-
LexiconUnion,
36
36
-
LexiconBlob,
37
37
-
LexiconBytes,
38
38
-
} from "./types.js";
28
28
+
LexiconDoc,
29
29
+
LexUserType,
30
30
+
LexObject,
31
31
+
LexArray,
32
32
+
LexBlob,
33
33
+
LexPrimitive,
34
34
+
LexIpldType,
35
35
+
LexRefVariant,
36
36
+
} from "@atproto/lexicon";
37
37
+
39
38
import {
40
39
getMaxGraphemes,
41
40
getMinGraphemes,
···
91
90
atIdentifier: "at-identifier",
92
91
};
93
92
93
93
+
// Array items can only be: primitives, IPLD types, refs/unions, or blobs
94
94
+
type LexArrayItem = LexPrimitive | LexIpldType | LexRefVariant | LexBlob;
95
95
+
94
96
export class TylexEmitter {
95
95
-
private lexicons = new Map<string, LexiconDocument>();
97
97
+
private lexicons = new Map<string, LexiconDoc>();
96
98
private currentLexiconId: string | null = null;
97
99
98
100
constructor(
···
258
260
this.currentLexiconId = null;
259
261
}
260
262
261
261
-
private createLexicon(id: string, ns: Namespace): LexiconDocument {
263
263
+
private createLexicon(id: string, ns: Namespace): LexiconDoc {
262
264
const description = getDoc(this.program, ns);
263
263
-
const lexicon: LexiconDocument = description
265
265
+
const lexicon: LexiconDoc = description
264
266
? { lexicon: 1, id, description, defs: {} }
265
267
: { lexicon: 1, id, defs: {} };
266
268
return lexicon;
···
287
289
return modelDef;
288
290
}
289
291
290
290
-
private addDefs(lexicon: LexiconDocument, ns: Namespace, models: Model[]) {
292
292
+
private addDefs(lexicon: LexiconDoc, ns: Namespace, models: Model[]) {
291
293
for (const model of models) {
292
294
this.addModelToDefs(lexicon, model);
293
295
}
···
301
303
}
302
304
}
303
305
304
304
-
private addModelToDefs(lexicon: LexiconDocument, model: Model) {
306
306
+
private addModelToDefs(lexicon: LexiconDoc, model: Model) {
305
307
if (model.name[0] !== model.name[0].toUpperCase()) {
306
308
this.program.reportDiagnostic({
307
309
code: "invalid-model-name",
···
338
340
lexicon.defs[defName] = this.addDescription(modelDef, description);
339
341
}
340
342
341
341
-
private addScalarToDefs(lexicon: LexiconDocument, scalar: Scalar) {
343
343
+
private addScalarToDefs(lexicon: LexiconDoc, scalar: Scalar) {
342
344
if (scalar.namespace?.name === "TypeSpec") return;
343
345
if (scalar.baseScalar?.namespace?.name === "TypeSpec") return;
344
346
···
348
350
lexicon.defs[defName] = this.addDescription(scalarDef, description);
349
351
}
350
352
351
351
-
private addUnionToDefs(lexicon: LexiconDocument, union: Union) {
353
353
+
private addUnionToDefs(lexicon: LexiconDoc, union: Union) {
352
354
const name = union.name;
353
355
if (!name) return;
354
356
···
385
387
);
386
388
}
387
389
388
388
-
private createBlobDef(model: Model): LexiconBlob {
389
389
-
const blobDef: LexiconBlob = { type: "blob" };
390
390
+
private createBlobDef(model: Model): LexBlob {
391
391
+
const blobDef: LexBlob = { type: "blob" };
390
392
391
393
if (isTemplateInstance(model)) {
392
394
const templateArgs = model.templateMapper?.args;
···
424
426
private processUnion(
425
427
unionType: Union,
426
428
prop?: ModelProperty,
427
427
-
): LexiconDefinition | null {
429
429
+
): LexUserType | null {
428
430
// Parse union variants
429
431
const variants = this.parseUnionVariants(unionType);
430
432
···
566
568
unionType: Union,
567
569
numericLiterals: number[],
568
570
prop?: ModelProperty,
569
569
-
): LexiconDefinition {
571
571
+
): LexUserType {
570
572
const primitive: any = {
571
573
type: "integer",
572
574
enum: numericLiterals,
···
592
594
unionType: Union,
593
595
booleanLiterals: boolean[],
594
596
prop?: ModelProperty,
595
595
-
): LexiconDefinition {
597
597
+
): LexUserType {
596
598
const primitive: any = {
597
599
type: "boolean",
598
600
enum: booleanLiterals,
···
618
620
unionType: Union,
619
621
stringLiterals: string[],
620
622
prop?: ModelProperty,
621
621
-
): LexiconDefinition {
623
623
+
): LexUserType {
622
624
// Use "enum" for @closed unions, "knownValues" for open unions
623
625
const isClosedUnion = isClosed(this.program, unionType);
624
626
const primitive: any = {
···
659
661
unionType: Union,
660
662
variants: ReturnType<typeof this.parseUnionVariants>,
661
663
prop?: ModelProperty,
662
662
-
): LexiconDefinition | null {
664
664
+
): LexUserType | null {
663
665
// Validate: cannot mix refs and string literals
664
666
if (variants.stringLiterals.length > 0) {
665
667
this.program.reportDiagnostic({
···
698
700
}
699
701
700
702
private addOperationToDefs(
701
701
-
lexicon: LexiconDocument,
703
703
+
lexicon: LexiconDoc,
702
704
operation: any,
703
705
defName: string,
704
706
) {
···
917
919
private modelToLexiconObject(
918
920
model: Model,
919
921
includeModelDescription: boolean = true,
920
920
-
): LexiconObject {
922
922
+
): LexObject {
921
923
const required: string[] = [];
922
924
const nullable: string[] = [];
923
925
const properties: any = {};
···
979
981
type: Type,
980
982
prop?: ModelProperty,
981
983
isDefining?: boolean,
982
982
-
): LexiconDefinition | null {
984
984
+
): LexUserType | null {
983
985
const propDesc = prop ? getDoc(this.program, prop) : undefined;
984
986
985
987
switch (type.kind) {
···
1007
1009
scalar: Scalar,
1008
1010
prop?: ModelProperty,
1009
1011
propDesc?: string,
1010
1010
-
): LexiconDefinition | null {
1012
1012
+
): LexUserType | null {
1011
1013
const primitive = this.scalarToLexiconPrimitive(scalar, prop);
1012
1014
if (!primitive) return null;
1013
1015
···
1027
1029
model: Model,
1028
1030
prop?: ModelProperty,
1029
1031
propDesc?: string,
1030
1030
-
): LexiconDefinition | null {
1032
1032
+
): LexUserType | null {
1031
1033
// 1. Check for Blob type
1032
1034
if (this.isBlob(model)) {
1033
1035
return this.addDescription(this.createBlobDef(model), propDesc);
···
1064
1066
prop?: ModelProperty,
1065
1067
isDefining?: boolean,
1066
1068
propDesc?: string,
1067
1067
-
): LexiconDefinition | null {
1069
1069
+
): LexUserType | null {
1068
1070
// Check if this is a named union that should be referenced
1069
1071
if (!isDefining) {
1070
1072
const unionRef = this.getUnionReference(unionType);
···
1079
1081
private scalarToLexiconPrimitive(
1080
1082
scalar: Scalar,
1081
1083
prop?: ModelProperty,
1082
1082
-
): LexiconDefinition | null {
1084
1084
+
): LexUserType | null {
1083
1085
// Check if this scalar (or its base) is bytes type
1084
1086
const isBytes = this.isScalarBytes(scalar);
1085
1087
···
1307
1309
private modelToLexiconArray(
1308
1310
model: Model,
1309
1311
prop?: ModelProperty,
1310
1310
-
): LexiconArray | null {
1312
1312
+
): LexArray | null {
1311
1313
const arrayModel = model.sourceModel || model;
1312
1314
const itemType = arrayModel.templateMapper?.args?.[0];
1313
1315
···
1323
1325
return null;
1324
1326
}
1325
1327
1326
1326
-
const arrayDef: LexiconArray = { type: "array", items: itemDef };
1328
1328
+
const arrayDef: LexArray = {
1329
1329
+
type: "array",
1330
1330
+
items: itemDef as LexArrayItem,
1331
1331
+
};
1327
1332
1328
1333
if (prop) {
1329
1334
const maxItems = getMaxItems(this.program, prop);
-153
packages/emitter/src/types.ts
···
1
1
-
export interface LexiconDocument {
2
2
-
lexicon: 1;
3
3
-
id: string;
4
4
-
revision?: number;
5
5
-
description?: string;
6
6
-
defs: Record<string, LexiconDefinition>;
7
7
-
}
8
8
-
9
9
-
export type LexiconDefinition =
10
10
-
| LexiconRecord
11
11
-
| LexiconQuery
12
12
-
| LexiconProcedure
13
13
-
| LexiconSubscription
14
14
-
| LexiconObject
15
15
-
| LexiconToken
16
16
-
| LexiconArray
17
17
-
| LexiconBlob
18
18
-
| LexiconPrimitive
19
19
-
| LexiconRef
20
20
-
| LexiconUnion
21
21
-
| LexiconUnknown
22
22
-
| LexiconCidLink
23
23
-
| LexiconBytes
24
24
-
| LexiconParams;
25
25
-
26
26
-
export interface LexiconRecord {
27
27
-
type: "record";
28
28
-
description?: string;
29
29
-
key?: string;
30
30
-
record: LexiconObject;
31
31
-
}
32
32
-
33
33
-
export interface LexiconQuery {
34
34
-
type: "query";
35
35
-
description?: string;
36
36
-
parameters?: LexiconParams;
37
37
-
output?: LexiconXrpcBody;
38
38
-
errors?: LexiconXrpcError[];
39
39
-
}
40
40
-
41
41
-
export interface LexiconProcedure {
42
42
-
type: "procedure";
43
43
-
description?: string;
44
44
-
parameters?: LexiconParams;
45
45
-
input?: LexiconXrpcBody;
46
46
-
output?: LexiconXrpcBody;
47
47
-
errors?: LexiconXrpcError[];
48
48
-
}
49
49
-
50
50
-
export interface LexiconSubscription {
51
51
-
type: "subscription";
52
52
-
description?: string;
53
53
-
parameters?: LexiconParams;
54
54
-
message?: LexiconXrpcMessage;
55
55
-
errors?: LexiconXrpcError[];
56
56
-
}
57
57
-
58
58
-
export interface LexiconParams {
59
59
-
type: "params";
60
60
-
description?: string;
61
61
-
required?: string[];
62
62
-
properties: Record<string, LexiconPrimitive | LexiconArray>;
63
63
-
}
64
64
-
65
65
-
export interface LexiconObject {
66
66
-
type: "object";
67
67
-
description?: string;
68
68
-
required?: string[];
69
69
-
nullable?: string[];
70
70
-
properties?: Record<string, LexiconDefinition>;
71
71
-
}
72
72
-
73
73
-
export interface LexiconArray {
74
74
-
type: "array";
75
75
-
description?: string;
76
76
-
items: LexiconDefinition;
77
77
-
minLength?: number;
78
78
-
maxLength?: number;
79
79
-
}
80
80
-
81
81
-
export interface LexiconToken {
82
82
-
type: "token";
83
83
-
description?: string;
84
84
-
}
85
85
-
86
86
-
export interface LexiconBlob {
87
87
-
type: "blob";
88
88
-
description?: string;
89
89
-
accept?: string[];
90
90
-
maxSize?: number;
91
91
-
}
92
92
-
93
93
-
export interface LexiconPrimitive {
94
94
-
type: "string" | "boolean" | "integer" | "number";
95
95
-
description?: string;
96
96
-
default?: any;
97
97
-
const?: any;
98
98
-
enum?: string[] | number[];
99
99
-
minimum?: number;
100
100
-
maximum?: number;
101
101
-
minLength?: number;
102
102
-
maxLength?: number;
103
103
-
minGraphemes?: number;
104
104
-
maxGraphemes?: number;
105
105
-
format?: string;
106
106
-
knownValues?: string[];
107
107
-
}
108
108
-
109
109
-
export interface LexiconRef {
110
110
-
type: "ref";
111
111
-
description?: string;
112
112
-
ref: string;
113
113
-
}
114
114
-
115
115
-
export interface LexiconUnion {
116
116
-
type: "union";
117
117
-
description?: string;
118
118
-
refs: string[];
119
119
-
closed?: boolean;
120
120
-
}
121
121
-
122
122
-
export interface LexiconUnknown {
123
123
-
type: "unknown";
124
124
-
description?: string;
125
125
-
}
126
126
-
127
127
-
export interface LexiconCidLink {
128
128
-
type: "cid-link";
129
129
-
description?: string;
130
130
-
}
131
131
-
132
132
-
export interface LexiconBytes {
133
133
-
type: "bytes";
134
134
-
description?: string;
135
135
-
minLength?: number;
136
136
-
maxLength?: number;
137
137
-
}
138
138
-
139
139
-
export interface LexiconXrpcBody {
140
140
-
description?: string;
141
141
-
encoding: string;
142
142
-
schema?: LexiconDefinition;
143
143
-
}
144
144
-
145
145
-
export interface LexiconXrpcMessage {
146
146
-
description?: string;
147
147
-
schema?: LexiconDefinition;
148
148
-
}
149
149
-
150
150
-
export interface LexiconXrpcError {
151
151
-
name: string;
152
152
-
description?: string;
153
153
-
}