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
yea
danabra.mov
5 months ago
4ef878cf
81f4c092
+48
-61
2 changed files
expand all
collapse all
unified
split
packages
emitter
src
emitter.ts
example
src
lexicons.ts
+38
-51
packages/emitter/src/emitter.ts
···
76
76
outputDir: string;
77
77
}
78
78
79
79
-
// Constants for atproto format scalars
80
80
-
const FORMAT_SCALARS = new Set([
81
81
-
"datetime",
82
82
-
"did",
83
83
-
"handle",
84
84
-
"atUri",
85
85
-
"cid",
86
86
-
"tid",
87
87
-
"nsid",
88
88
-
"recordKey",
89
89
-
"uri",
90
90
-
"language",
91
91
-
"atIdentifier",
92
92
-
"bytes",
93
93
-
"cidLink",
94
94
-
]);
95
95
-
96
96
-
const FORMAT_MAP: Record<string, string> = {
79
79
+
// Constants for string format scalars (type: "string" with format field)
80
80
+
const STRING_FORMAT_MAP: Record<string, string> = {
97
81
did: "did",
98
82
handle: "handle",
99
83
atUri: "at-uri",
···
1079
1063
// Determine description: prop description, or inherited scalar description for custom scalars
1080
1064
let description = propDesc;
1081
1065
if (!description && scalar.baseScalar && scalar.namespace?.name !== "TypeSpec") {
1082
1082
-
if (!FORMAT_SCALARS.has(scalar.name)) {
1066
1066
+
// Don't inherit description for built-in scalars (formats, bytes, cidLink)
1067
1067
+
const isBuiltInScalar = STRING_FORMAT_MAP[scalar.name] ||
1068
1068
+
this.isScalarBytes(scalar) ||
1069
1069
+
this.isScalarCidLink(scalar);
1070
1070
+
if (!isBuiltInScalar) {
1083
1071
description = getDoc(this.program, scalar);
1084
1072
}
1085
1073
}
···
1180
1168
let primitive = this.getBasePrimitiveType(scalar);
1181
1169
1182
1170
// Apply format if applicable
1183
1183
-
const format = FORMAT_MAP[scalar.name];
1171
1171
+
const format = STRING_FORMAT_MAP[scalar.name];
1184
1172
if (format && primitive.type === "string") {
1185
1173
primitive = { ...primitive, format };
1186
1174
}
···
1217
1205
) {
1218
1206
return { type: "integer" };
1219
1207
} else if (["float32", "float64"].includes(scalar.name)) {
1220
1220
-
return { type: "integer" }; // Note: lexicon uses integer for floats
1208
1208
+
// Lexicon does not support floating-point numbers
1209
1209
+
this.program.reportDiagnostic({
1210
1210
+
code: "float-not-supported",
1211
1211
+
severity: "error",
1212
1212
+
message: `Floating-point type "${scalar.name}" is not supported in Lexicon. Use integer instead.`,
1213
1213
+
target: scalar,
1214
1214
+
});
1215
1215
+
return { type: "integer" };
1221
1216
}
1222
1217
return { type: "string" };
1223
1218
}
···
1342
1337
);
1343
1338
}
1344
1339
1345
1345
-
private getModelReference(model: Model): string | null {
1346
1346
-
if (!model.name || !model.namespace || model.namespace.name === "TypeSpec")
1347
1347
-
return null;
1340
1340
+
private getReference(
1341
1341
+
entity: Model | Union,
1342
1342
+
name: string | undefined,
1343
1343
+
namespace: Namespace | undefined,
1344
1344
+
): string | null {
1345
1345
+
if (!name || !namespace || namespace.name === "TypeSpec") return null;
1348
1346
1349
1349
-
// If model is marked as @inline, don't create a reference - inline it instead
1350
1350
-
if (isInline(this.program, model)) return null;
1347
1347
+
// If entity is marked as @inline, don't create a reference - inline it instead
1348
1348
+
if (isInline(this.program, entity)) return null;
1351
1349
1352
1352
-
const namespaceName = getNamespaceFullName(model.namespace);
1350
1350
+
const namespaceName = getNamespaceFullName(namespace);
1353
1351
if (!namespaceName) return null;
1354
1352
1355
1355
-
const defName = model.name.charAt(0).toLowerCase() + model.name.slice(1);
1353
1353
+
const defName = name.charAt(0).toLowerCase() + name.slice(1);
1356
1354
1355
1355
+
// Local reference (same namespace)
1357
1356
if (
1358
1357
this.currentLexiconId === namespaceName ||
1359
1358
this.currentLexiconId === `${namespaceName}.defs`
···
1361
1360
return `#${defName}`;
1362
1361
}
1363
1362
1364
1364
-
return model.name === "Main"
1365
1365
-
? namespaceName
1366
1366
-
: `${namespaceName}#${defName}`;
1367
1367
-
}
1368
1368
-
1369
1369
-
private getUnionReference(union: Union): string | null {
1370
1370
-
const unionName = union.name;
1371
1371
-
const namespace = union.namespace;
1372
1372
-
if (!unionName || !namespace || namespace.name === "TypeSpec") return null;
1373
1373
-
1374
1374
-
// If union is marked as @inline, don't create a reference - inline it instead
1375
1375
-
if (isInline(this.program, union)) return null;
1376
1376
-
1377
1377
-
const namespaceName = getNamespaceFullName(namespace);
1378
1378
-
if (!namespaceName) return null;
1379
1379
-
1380
1380
-
const defName = unionName.charAt(0).toLowerCase() + unionName.slice(1);
1381
1381
-
1382
1382
-
if (
1383
1383
-
this.currentLexiconId === namespaceName ||
1384
1384
-
this.currentLexiconId === `${namespaceName}.defs`
1385
1385
-
) {
1386
1386
-
return `#${defName}`;
1363
1363
+
// Cross-namespace reference: Main models reference just the namespace
1364
1364
+
if (entity.kind === "Model" && name === "Main") {
1365
1365
+
return namespaceName;
1387
1366
}
1388
1367
1389
1368
return `${namespaceName}#${defName}`;
1369
1369
+
}
1370
1370
+
1371
1371
+
private getModelReference(model: Model): string | null {
1372
1372
+
return this.getReference(model, model.name, model.namespace);
1373
1373
+
}
1374
1374
+
1375
1375
+
private getUnionReference(union: Union): string | null {
1376
1376
+
return this.getReference(union, union.name, union.namespace);
1390
1377
}
1391
1378
1392
1379
private modelToLexiconArray(
+10
-10
packages/example/src/lexicons.ts
···
16
16
defs: {
17
17
postRef: {
18
18
type: 'object',
19
19
-
description: 'Reference to a post',
20
20
-
required: ['uri', 'cid'],
21
19
properties: {
22
20
uri: {
23
21
type: 'string',
···
28
26
description: 'CID of the post',
29
27
},
30
28
},
29
29
+
description: 'Reference to a post',
30
30
+
required: ['uri', 'cid'],
31
31
},
32
32
replyRef: {
33
33
type: 'object',
34
34
-
description: 'Reference to a parent post in a reply chain',
35
35
-
required: ['root', 'parent'],
36
34
properties: {
37
35
root: {
38
36
type: 'ref',
···
45
43
description: 'Direct parent post being replied to',
46
44
},
47
45
},
46
46
+
description: 'Reference to a parent post in a reply chain',
47
47
+
required: ['root', 'parent'],
48
48
},
49
49
entity: {
50
50
type: 'object',
51
51
-
description: 'Text entity (mention, link, or tag)',
52
52
-
required: ['start', 'end', 'type', 'value'],
53
51
properties: {
54
52
start: {
55
53
type: 'integer',
···
68
66
description: 'Entity value (handle, URL, or tag)',
69
67
},
70
68
},
69
69
+
description: 'Text entity (mention, link, or tag)',
70
70
+
required: ['start', 'end', 'type', 'value'],
71
71
},
72
72
notificationType: {
73
73
type: 'string',
···
85
85
key: 'tid',
86
86
record: {
87
87
type: 'object',
88
88
-
required: ['subject', 'createdAt'],
89
88
properties: {
90
89
subject: {
91
90
type: 'string',
···
97
96
description: 'When the follow was created',
98
97
},
99
98
},
99
99
+
required: ['subject', 'createdAt'],
100
100
},
101
101
description: 'A follow relationship',
102
102
},
···
111
111
key: 'tid',
112
112
record: {
113
113
type: 'object',
114
114
-
required: ['subject', 'createdAt'],
115
114
properties: {
116
115
subject: {
117
116
type: 'ref',
···
124
123
description: 'When the like was created',
125
124
},
126
125
},
126
126
+
required: ['subject', 'createdAt'],
127
127
},
128
128
description: 'A like on a post',
129
129
},
···
138
138
key: 'tid',
139
139
record: {
140
140
type: 'object',
141
141
-
required: ['text', 'createdAt'],
142
141
properties: {
143
142
text: {
144
143
type: 'string',
···
170
169
description: 'Post the user is replying to',
171
170
},
172
171
},
172
172
+
required: ['text', 'createdAt'],
173
173
},
174
174
description: 'A post in the feed',
175
175
},
···
216
216
key: 'tid',
217
217
record: {
218
218
type: 'object',
219
219
-
required: ['subject', 'createdAt'],
220
219
properties: {
221
220
subject: {
222
221
type: 'ref',
···
229
228
description: 'When the repost was created',
230
229
},
231
230
},
231
231
+
required: ['subject', 'createdAt'],
232
232
},
233
233
description: 'A repost of another post',
234
234
},