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
3b6bf59a
62c241c1
+625
-357
3 changed files
expand all
collapse all
unified
split
packages
emitter
src
emitter.ts
example
src
lexicons.ts
types
app
example
defs.ts
+611
-357
packages/emitter/src/emitter.ts
···
53
53
outputDir: string;
54
54
}
55
55
56
56
+
// Constants for atproto format scalars
57
57
+
const FORMAT_SCALARS = new Set([
58
58
+
"datetime",
59
59
+
"did",
60
60
+
"handle",
61
61
+
"atUri",
62
62
+
"cid",
63
63
+
"tid",
64
64
+
"nsid",
65
65
+
"recordKey",
66
66
+
"uri",
67
67
+
"language",
68
68
+
"atIdentifier",
69
69
+
"bytes",
70
70
+
"utcDateTime",
71
71
+
"offsetDateTime",
72
72
+
"plainDate",
73
73
+
"plainTime",
74
74
+
]);
75
75
+
76
76
+
const FORMAT_MAP: Record<string, string> = {
77
77
+
did: "did",
78
78
+
handle: "handle",
79
79
+
atUri: "at-uri",
80
80
+
datetime: "datetime",
81
81
+
cid: "cid",
82
82
+
tid: "tid",
83
83
+
nsid: "nsid",
84
84
+
recordKey: "record-key",
85
85
+
uri: "uri",
86
86
+
language: "language",
87
87
+
atIdentifier: "at-identifier",
88
88
+
utcDateTime: "datetime",
89
89
+
offsetDateTime: "datetime",
90
90
+
plainDate: "datetime",
91
91
+
plainTime: "datetime",
92
92
+
};
93
93
+
56
94
export class TlexEmitter {
57
95
private lexicons = new Map<string, LexiconDocument>();
58
96
private currentLexiconId: string | null = null;
···
78
116
private processNamespace(ns: any) {
79
117
const fullName = getNamespaceFullName(ns);
80
118
81
81
-
if (fullName && !fullName.startsWith("TypeSpec")) {
82
82
-
const hasModels = ns.models.size > 0;
83
83
-
const hasScalars = ns.scalars.size > 0;
84
84
-
const hasUnions = ns.unions?.size > 0;
85
85
-
const hasOperations = ns.operations?.size > 0;
86
86
-
const hasChildNamespaces = ns.namespaces.size > 0;
87
87
-
const hasContent = hasModels || hasScalars || hasUnions;
119
119
+
// Skip TypeSpec internal namespaces
120
120
+
if (!fullName || fullName.startsWith("TypeSpec")) {
121
121
+
for (const [_, childNs] of ns.namespaces) {
122
122
+
this.processNamespace(childNs);
123
123
+
}
124
124
+
return;
125
125
+
}
126
126
+
127
127
+
const namespaceType = this.classifyNamespace(ns);
88
128
89
89
-
if (hasOperations) {
129
129
+
switch (namespaceType) {
130
130
+
case "operation":
90
131
this.emitOperationLexicon(ns, fullName);
91
91
-
} else if (hasContent && !hasChildNamespaces) {
132
132
+
break;
133
133
+
case "content":
92
134
this.emitContentLexicon(ns, fullName);
93
93
-
} else if (hasContent && hasChildNamespaces) {
135
135
+
break;
136
136
+
case "defs":
94
137
this.emitDefsLexicon(ns, fullName);
95
95
-
}
138
138
+
break;
139
139
+
case "empty":
140
140
+
// Empty namespace, skip
141
141
+
break;
96
142
}
97
143
144
144
+
// Recursively process child namespaces
98
145
for (const [_, childNs] of ns.namespaces) {
99
146
this.processNamespace(childNs);
100
147
}
101
148
}
102
149
150
150
+
private classifyNamespace(
151
151
+
ns: any,
152
152
+
): "operation" | "content" | "defs" | "empty" {
153
153
+
const hasModels = ns.models.size > 0;
154
154
+
const hasScalars = ns.scalars.size > 0;
155
155
+
const hasUnions = ns.unions?.size > 0;
156
156
+
const hasOperations = ns.operations?.size > 0;
157
157
+
const hasChildNamespaces = ns.namespaces.size > 0;
158
158
+
const hasContent = hasModels || hasScalars || hasUnions;
159
159
+
160
160
+
if (hasOperations) {
161
161
+
return "operation";
162
162
+
}
163
163
+
164
164
+
if (hasContent && hasChildNamespaces) {
165
165
+
return "defs";
166
166
+
}
167
167
+
168
168
+
if (hasContent && !hasChildNamespaces) {
169
169
+
return "content";
170
170
+
}
171
171
+
172
172
+
return "empty";
173
173
+
}
174
174
+
103
175
private emitContentLexicon(ns: any, fullName: string) {
104
176
const models = [...ns.models.values()];
105
177
const isDefsFile = fullName.endsWith(".defs");
···
117
189
118
190
if (mainModel) {
119
191
lexicon.defs.main = this.createMainDef(mainModel);
120
120
-
this.addDefs(lexicon, ns, models.filter((m) => m.name !== "Main"));
192
192
+
this.addDefs(
193
193
+
lexicon,
194
194
+
ns,
195
195
+
models.filter((m) => m.name !== "Main"),
196
196
+
);
121
197
} else {
122
198
this.addDefs(lexicon, ns, models);
123
199
}
···
127
203
}
128
204
129
205
private emitDefsLexicon(ns: any, fullName: string) {
130
130
-
const lexiconId = fullName.endsWith(".defs") ? fullName : fullName + ".defs";
206
206
+
const lexiconId = fullName.endsWith(".defs")
207
207
+
? fullName
208
208
+
: fullName + ".defs";
131
209
this.currentLexiconId = lexiconId;
132
210
const lexicon = this.createLexicon(lexiconId, ns);
133
211
this.addDefs(lexicon, ns, [...ns.models.values()]);
···
140
218
const lexicon = this.createLexicon(fullName, ns);
141
219
142
220
const mainOp = [...ns.operations].find(
143
143
-
([name]) => name === "main" || name === "Main"
221
221
+
([name]) => name === "main" || name === "Main",
144
222
)?.[1];
145
223
146
224
if (mainOp) {
···
153
231
}
154
232
}
155
233
156
156
-
this.addDefs(lexicon, ns, [...ns.models.values()].filter((m) => m.name !== "Main"));
234
234
+
this.addDefs(
235
235
+
lexicon,
236
236
+
ns,
237
237
+
[...ns.models.values()].filter((m) => m.name !== "Main"),
238
238
+
);
157
239
this.lexicons.set(fullName, lexicon);
158
240
this.currentLexiconId = null;
159
241
}
···
171
253
const modelDef = this.modelToLexiconObject(mainModel, !!modelDescription);
172
254
173
255
if (recordKey) {
174
174
-
const recordDef: any = { type: "record", key: recordKey, record: modelDef };
256
256
+
const recordDef: any = {
257
257
+
type: "record",
258
258
+
key: recordKey,
259
259
+
record: modelDef,
260
260
+
};
175
261
if (modelDescription) {
176
262
recordDef.description = modelDescription;
177
263
delete modelDef.description;
···
213
299
const description = getDoc(this.program, model);
214
300
215
301
if (isToken(this.program, model)) {
216
216
-
lexicon.defs[defName] = this.addDescription({ type: "token" }, description);
302
302
+
lexicon.defs[defName] = this.addDescription(
303
303
+
{ type: "token" },
304
304
+
description,
305
305
+
);
217
306
return;
218
307
}
219
308
···
246
335
const unionDef: any = this.typeToLexiconDefinition(union, undefined, true);
247
336
if (!unionDef) return;
248
337
249
249
-
if (unionDef.type === "union" || (unionDef.type === "string" && unionDef.knownValues)) {
338
338
+
if (
339
339
+
unionDef.type === "union" ||
340
340
+
(unionDef.type === "string" && unionDef.knownValues)
341
341
+
) {
250
342
const defName = name.charAt(0).toLowerCase() + name.slice(1);
251
343
const description = getDoc(this.program, union);
252
344
lexicon.defs[defName] = this.addDescription(unionDef, description);
···
260
352
return obj;
261
353
}
262
354
355
355
+
private isBlob(model: Model): boolean {
356
356
+
return !!(
357
357
+
isBlob(this.program, model) ||
358
358
+
(isTemplateInstance(model) &&
359
359
+
model.templateNode &&
360
360
+
isBlob(this.program, model.templateNode as any)) ||
361
361
+
(model.baseModel && isBlob(this.program, model.baseModel))
362
362
+
);
363
363
+
}
364
364
+
365
365
+
private isClosedUnionTemplate(model: Model): boolean {
366
366
+
return !!(
367
367
+
model.name === "Closed" ||
368
368
+
(model.node && (model.node as any).symbol?.name === "Closed") ||
369
369
+
(isTemplateInstance(model) &&
370
370
+
model.node &&
371
371
+
(model.node as any).symbol?.name === "Closed")
372
372
+
);
373
373
+
}
374
374
+
263
375
private createBlobDef(model: Model): LexiconBlob {
264
376
const blobDef: LexiconBlob = { type: "blob" };
265
377
···
278
390
if (!acceptTypes.length) acceptTypes = undefined;
279
391
}
280
392
} else if (acceptArg && Array.isArray(acceptArg.value)) {
281
281
-
const values = acceptArg.value.filter((v: any) => typeof v === "string");
393
393
+
const values = acceptArg.value.filter(
394
394
+
(v: any) => typeof v === "string",
395
395
+
);
282
396
if (values.length) acceptTypes = values;
283
397
}
284
398
285
399
if (acceptTypes) blobDef.accept = acceptTypes;
286
400
287
401
const maxSizeArg = templateArgs[1] as any;
288
288
-
const maxSize = maxSizeArg?.value ?? (maxSizeArg?.type?.kind === "Number" ? Number(maxSizeArg.type.value) : undefined);
402
402
+
const maxSize =
403
403
+
maxSizeArg?.value ??
404
404
+
(maxSizeArg?.type?.kind === "Number"
405
405
+
? Number(maxSizeArg.type.value)
406
406
+
: undefined);
289
407
if (maxSize !== undefined && maxSize !== 0) blobDef.maxSize = maxSize;
290
408
}
291
409
}
···
293
411
return blobDef;
294
412
}
295
413
296
296
-
private processUnion(unionType: Union, prop?: ModelProperty): LexiconDefinition | null {
414
414
+
private processUnion(
415
415
+
unionType: Union,
416
416
+
prop?: ModelProperty,
417
417
+
): LexiconDefinition | null {
418
418
+
// Parse union variants
419
419
+
const variants = this.parseUnionVariants(unionType);
420
420
+
421
421
+
// Case 1: String enum with known values (string literals + string type)
422
422
+
if (variants.isStringEnum) {
423
423
+
return this.createStringEnumDef(unionType, variants.stringLiterals, prop);
424
424
+
}
425
425
+
426
426
+
// Case 2: Model reference union
427
427
+
if (variants.unionRefs.length > 0) {
428
428
+
return this.createUnionRefDef(unionType, variants, prop);
429
429
+
}
430
430
+
431
431
+
// Case 3: Empty or invalid union
432
432
+
if (variants.stringLiterals.length === 0 && !variants.hasUnknown) {
433
433
+
this.program.reportDiagnostic({
434
434
+
code: "union-empty",
435
435
+
severity: "error",
436
436
+
message: `Union has no variants. Atproto unions must contain either model references or string literals.`,
437
437
+
target: unionType,
438
438
+
});
439
439
+
}
440
440
+
441
441
+
return null;
442
442
+
}
443
443
+
444
444
+
private parseUnionVariants(unionType: Union) {
297
445
const unionRefs: string[] = [];
298
446
const stringLiterals: string[] = [];
299
447
let hasStringType = false;
300
448
let hasUnknown = false;
301
449
302
450
for (const variant of unionType.variants.values()) {
303
303
-
if (variant.type.kind === "Model") {
304
304
-
const ref = this.getModelReference(variant.type as Model);
305
305
-
if (ref) unionRefs.push(ref);
306
306
-
} else if (variant.type.kind === "String") {
307
307
-
stringLiterals.push((variant.type as any).value);
308
308
-
} else if (variant.type.kind === "Scalar" && (variant.type as Scalar).name === "string") {
309
309
-
hasStringType = true;
310
310
-
} else if (variant.type.kind === "Intrinsic") {
311
311
-
const intrinsicName = (variant.type as any).name;
312
312
-
if (intrinsicName === "unknown" || intrinsicName === "never") {
313
313
-
hasUnknown = true;
314
314
-
}
451
451
+
switch (variant.type.kind) {
452
452
+
case "Model":
453
453
+
const ref = this.getModelReference(variant.type as Model);
454
454
+
if (ref) unionRefs.push(ref);
455
455
+
break;
456
456
+
case "String":
457
457
+
stringLiterals.push((variant.type as any).value);
458
458
+
break;
459
459
+
case "Scalar":
460
460
+
if ((variant.type as Scalar).name === "string") {
461
461
+
hasStringType = true;
462
462
+
}
463
463
+
break;
464
464
+
case "Intrinsic":
465
465
+
const intrinsicName = (variant.type as any).name;
466
466
+
if (intrinsicName === "unknown" || intrinsicName === "never") {
467
467
+
hasUnknown = true;
468
468
+
}
469
469
+
break;
315
470
}
316
471
}
317
472
318
318
-
if (stringLiterals.length && hasStringType && !unionRefs.length) {
319
319
-
const primitive: any = { type: "string", knownValues: stringLiterals };
320
320
-
321
321
-
const maxLength = getMaxLength(this.program, unionType);
322
322
-
if (maxLength !== undefined) primitive.maxLength = maxLength;
473
473
+
const isStringEnum =
474
474
+
stringLiterals.length > 0 && hasStringType && unionRefs.length === 0;
323
475
324
324
-
const minLength = getMinLength(this.program, unionType);
325
325
-
if (minLength !== undefined) primitive.minLength = minLength;
476
476
+
return {
477
477
+
unionRefs,
478
478
+
stringLiterals,
479
479
+
hasStringType,
480
480
+
hasUnknown,
481
481
+
isStringEnum,
482
482
+
};
483
483
+
}
326
484
327
327
-
const maxGraphemes = getMaxGraphemes(this.program, unionType);
328
328
-
if (maxGraphemes !== undefined) primitive.maxGraphemes = maxGraphemes;
485
485
+
private createStringEnumDef(
486
486
+
unionType: Union,
487
487
+
stringLiterals: string[],
488
488
+
prop?: ModelProperty,
489
489
+
): LexiconDefinition {
490
490
+
const primitive: any = { type: "string", knownValues: stringLiterals };
329
491
330
330
-
const minGraphemes = getMinGraphemes(this.program, unionType);
331
331
-
if (minGraphemes !== undefined) primitive.minGraphemes = minGraphemes;
492
492
+
// Apply constraints
493
493
+
const maxLength = getMaxLength(this.program, unionType);
494
494
+
if (maxLength !== undefined) primitive.maxLength = maxLength;
332
495
333
333
-
if (prop) {
334
334
-
const propDesc = getDoc(this.program, prop);
335
335
-
if (propDesc) primitive.description = propDesc;
496
496
+
const minLength = getMinLength(this.program, unionType);
497
497
+
if (minLength !== undefined) primitive.minLength = minLength;
336
498
337
337
-
const defaultValue = (prop as any).default;
338
338
-
if (defaultValue?.value !== undefined && typeof defaultValue.value === "string") {
339
339
-
primitive.default = defaultValue.value;
340
340
-
}
341
341
-
}
342
342
-
return primitive;
343
343
-
}
499
499
+
const maxGraphemes = getMaxGraphemes(this.program, unionType);
500
500
+
if (maxGraphemes !== undefined) primitive.maxGraphemes = maxGraphemes;
344
501
345
345
-
if (unionRefs.length) {
346
346
-
if (stringLiterals.length) {
347
347
-
this.program.reportDiagnostic({
348
348
-
code: "union-mixed-refs-literals",
349
349
-
severity: "error",
350
350
-
message:
351
351
-
`Union contains both model references and string literals. Atproto unions must be either: ` +
352
352
-
`(1) model references only (type: "union"), or ` +
353
353
-
`(2) string literals + string type (type: "string" with knownValues). ` +
354
354
-
`Separate these into distinct fields or nested unions.`,
355
355
-
target: unionType,
356
356
-
});
357
357
-
return null;
358
358
-
}
502
502
+
const minGraphemes = getMinGraphemes(this.program, unionType);
503
503
+
if (minGraphemes !== undefined) primitive.minGraphemes = minGraphemes;
359
504
360
360
-
const unionDef: any = { type: "union", refs: unionRefs };
505
505
+
// Add property-specific metadata
506
506
+
if (prop) {
507
507
+
const propDesc = getDoc(this.program, prop);
508
508
+
if (propDesc) primitive.description = propDesc;
361
509
362
362
-
if (isClosed(this.program, unionType)) {
363
363
-
if (hasUnknown) {
364
364
-
this.program.reportDiagnostic({
365
365
-
code: "closed-open-union",
366
366
-
severity: "error",
367
367
-
message:
368
368
-
"@closed decorator cannot be used on open unions (unions containing 'unknown' or 'never'). Remove the @closed decorator or make the union closed by removing 'unknown'/'never'.",
369
369
-
target: unionType,
370
370
-
});
371
371
-
} else {
372
372
-
unionDef.closed = true;
373
373
-
}
510
510
+
const defaultValue = (prop as any).default;
511
511
+
if (
512
512
+
defaultValue?.value !== undefined &&
513
513
+
typeof defaultValue.value === "string"
514
514
+
) {
515
515
+
primitive.default = defaultValue.value;
374
516
}
375
375
-
376
376
-
const propDesc = prop ? getDoc(this.program, prop) : undefined;
377
377
-
return this.addDescription(unionDef, propDesc);
378
517
}
379
518
380
380
-
if (!stringLiterals.length && !hasUnknown) {
519
519
+
return primitive;
520
520
+
}
521
521
+
522
522
+
private createUnionRefDef(
523
523
+
unionType: Union,
524
524
+
variants: ReturnType<typeof this.parseUnionVariants>,
525
525
+
prop?: ModelProperty,
526
526
+
): LexiconDefinition | null {
527
527
+
// Validate: cannot mix refs and string literals
528
528
+
if (variants.stringLiterals.length > 0) {
381
529
this.program.reportDiagnostic({
382
382
-
code: "union-empty",
530
530
+
code: "union-mixed-refs-literals",
383
531
severity: "error",
384
384
-
message: `Union has no variants. Atproto unions must contain either model references or string literals.`,
532
532
+
message:
533
533
+
`Union contains both model references and string literals. Atproto unions must be either: ` +
534
534
+
`(1) model references only (type: "union"), or ` +
535
535
+
`(2) string literals + string type (type: "string" with knownValues). ` +
536
536
+
`Separate these into distinct fields or nested unions.`,
385
537
target: unionType,
386
538
});
539
539
+
return null;
387
540
}
388
541
389
389
-
return null;
542
542
+
const unionDef: any = { type: "union", refs: variants.unionRefs };
543
543
+
544
544
+
// Handle closed unions
545
545
+
if (isClosed(this.program, unionType)) {
546
546
+
if (variants.hasUnknown) {
547
547
+
this.program.reportDiagnostic({
548
548
+
code: "closed-open-union",
549
549
+
severity: "error",
550
550
+
message:
551
551
+
"@closed decorator cannot be used on open unions (unions containing 'unknown' or 'never'). " +
552
552
+
"Remove the @closed decorator or make the union closed by removing 'unknown'/'never'.",
553
553
+
target: unionType,
554
554
+
});
555
555
+
} else {
556
556
+
unionDef.closed = true;
557
557
+
}
558
558
+
}
559
559
+
560
560
+
const propDesc = prop ? getDoc(this.program, prop) : undefined;
561
561
+
return this.addDescription(unionDef, propDesc);
390
562
}
391
563
392
564
private addOperationToDefs(
···
441
613
private addProcedureParams(def: any, operation: any) {
442
614
if (!operation.parameters?.properties?.size) return;
443
615
444
444
-
const params = Array.from(operation.parameters.properties) as [string, any][];
616
616
+
const params = Array.from(operation.parameters.properties) as [
617
617
+
string,
618
618
+
any,
619
619
+
][];
445
620
const paramCount = params.length;
446
621
622
622
+
// Validate parameter count
447
623
if (paramCount > 2) {
448
624
this.program.reportDiagnostic({
449
625
code: "procedure-too-many-params",
450
626
severity: "error",
451
451
-
message: "Procedures can have at most 2 parameters (input and/or parameters)",
627
627
+
message:
628
628
+
"Procedures can have at most 2 parameters (input and/or parameters)",
452
629
target: operation,
453
630
});
454
631
return;
455
632
}
456
633
457
457
-
if (paramCount === 1) {
458
458
-
const [paramName, param] = params[0];
459
459
-
if (paramName !== "input") {
460
460
-
this.program.reportDiagnostic({
461
461
-
code: "procedure-invalid-param-name",
462
462
-
severity: "error",
463
463
-
message: `Procedure parameter must be named "input", got "${paramName}"`,
464
464
-
target: param,
465
465
-
});
466
466
-
}
467
467
-
this.addInput(def, param);
468
468
-
} else if (paramCount === 2) {
469
469
-
const [param1Name, param1] = params[0];
470
470
-
const [param2Name, param2] = params[1];
634
634
+
// Handle parameter count cases
635
635
+
switch (paramCount) {
636
636
+
case 1:
637
637
+
this.handleSingleProcedureParam(def, params[0], operation);
638
638
+
break;
639
639
+
case 2:
640
640
+
this.handleTwoProcedureParams(def, params[0], params[1], operation);
641
641
+
break;
642
642
+
}
643
643
+
}
644
644
+
645
645
+
private handleSingleProcedureParam(
646
646
+
def: any,
647
647
+
[paramName, param]: [string, any],
648
648
+
operation: any,
649
649
+
) {
650
650
+
// Validate parameter name
651
651
+
if (paramName !== "input") {
652
652
+
this.program.reportDiagnostic({
653
653
+
code: "procedure-invalid-param-name",
654
654
+
severity: "error",
655
655
+
message: `Procedure parameter must be named "input", got "${paramName}"`,
656
656
+
target: param,
657
657
+
});
658
658
+
return;
659
659
+
}
660
660
+
661
661
+
this.addInput(def, param);
662
662
+
}
663
663
+
664
664
+
private handleTwoProcedureParams(
665
665
+
def: any,
666
666
+
[param1Name, param1]: [string, any],
667
667
+
[param2Name, param2]: [string, any],
668
668
+
operation: any,
669
669
+
) {
670
670
+
// Validate first parameter (input)
671
671
+
if (param1Name !== "input") {
672
672
+
this.program.reportDiagnostic({
673
673
+
code: "procedure-invalid-first-param",
674
674
+
severity: "error",
675
675
+
message: `First parameter must be named "input", got "${param1Name}"`,
676
676
+
target: param1,
677
677
+
});
678
678
+
}
471
679
472
472
-
if (param1Name !== "input") {
473
473
-
this.program.reportDiagnostic({
474
474
-
code: "procedure-invalid-first-param",
475
475
-
severity: "error",
476
476
-
message: `First parameter must be named "input", got "${param1Name}"`,
477
477
-
target: param1,
478
478
-
});
479
479
-
}
680
680
+
// Validate second parameter (parameters)
681
681
+
if (param2Name !== "parameters") {
682
682
+
this.program.reportDiagnostic({
683
683
+
code: "procedure-invalid-second-param",
684
684
+
severity: "error",
685
685
+
message: `Second parameter must be named "parameters", got "${param2Name}"`,
686
686
+
target: param2,
687
687
+
});
688
688
+
}
480
689
481
481
-
if (param2Name !== "parameters") {
482
482
-
this.program.reportDiagnostic({
483
483
-
code: "procedure-invalid-second-param",
484
484
-
severity: "error",
485
485
-
message: `Second parameter must be named "parameters", got "${param2Name}"`,
486
486
-
target: param2,
487
487
-
});
488
488
-
}
690
690
+
// Validate that parameters is a plain object
691
691
+
if (param2.type.kind !== "Model" || (param2.type as any).name) {
692
692
+
this.program.reportDiagnostic({
693
693
+
code: "procedure-parameters-not-object",
694
694
+
severity: "error",
695
695
+
message:
696
696
+
"The 'parameters' parameter must be a plain object, not a model reference",
697
697
+
target: param2,
698
698
+
});
699
699
+
}
489
700
490
490
-
if (param2.type.kind !== "Model" || (param2.type as any).name) {
491
491
-
this.program.reportDiagnostic({
492
492
-
code: "procedure-parameters-not-object",
493
493
-
severity: "error",
494
494
-
message: "The 'parameters' parameter must be a plain object, not a model reference",
495
495
-
target: param2,
496
496
-
});
497
497
-
}
701
701
+
// Add input
702
702
+
this.addInput(def, param1);
498
703
499
499
-
this.addInput(def, param1);
704
704
+
// Add parameters
705
705
+
this.addParametersFromModel(def, param2.type as any);
706
706
+
}
500
707
501
501
-
const parametersModel = param2.type as any;
502
502
-
if (parametersModel.kind === "Model" && parametersModel.properties) {
503
503
-
const paramsObj: any = { type: "params", properties: {} };
504
504
-
const required: string[] = [];
708
708
+
private addParametersFromModel(def: any, parametersModel: any) {
709
709
+
if (parametersModel.kind !== "Model" || !parametersModel.properties) {
710
710
+
return;
711
711
+
}
505
712
506
506
-
for (const [propName, prop] of parametersModel.properties) {
507
507
-
const propDef = this.typeToLexiconDefinition(prop.type, prop);
508
508
-
if (propDef) {
509
509
-
paramsObj.properties[propName] = propDef;
510
510
-
if (!prop.optional) required.push(propName);
511
511
-
}
512
512
-
}
713
713
+
const paramsObj: any = { type: "params", properties: {} };
714
714
+
const required: string[] = [];
513
715
514
514
-
if (required.length) paramsObj.required = required;
515
515
-
def.parameters = paramsObj;
716
716
+
for (const [propName, prop] of parametersModel.properties) {
717
717
+
const propDef = this.typeToLexiconDefinition(prop.type, prop);
718
718
+
if (propDef) {
719
719
+
paramsObj.properties[propName] = propDef;
720
720
+
if (!prop.optional) {
721
721
+
required.push(propName);
722
722
+
}
516
723
}
517
724
}
725
725
+
726
726
+
if (required.length > 0) {
727
727
+
paramsObj.required = required;
728
728
+
}
729
729
+
730
730
+
def.parameters = paramsObj;
518
731
}
519
732
520
733
private addInput(def: any, param: any) {
···
558
771
if (errors?.length) def.errors = errors;
559
772
}
560
773
561
561
-
private visitModel(model: Model) {
562
562
-
// Skip template models
563
563
-
if (model.templateMapper || isTemplateInstance(model)) {
564
564
-
return;
565
565
-
}
566
566
-
567
567
-
// Get the lexicon ID from the namespace and model name
568
568
-
const namespace = model.namespace;
569
569
-
if (!namespace || namespace.name === "") {
570
570
-
return;
571
571
-
}
572
572
-
573
573
-
const lexiconId = this.getModelLexiconId(model);
574
574
-
if (!lexiconId) {
575
575
-
return;
576
576
-
}
577
577
-
578
578
-
// Create or get the lexicon document
579
579
-
let lexicon = this.lexicons.get(lexiconId);
580
580
-
if (!lexicon) {
581
581
-
lexicon = {
582
582
-
lexicon: 1,
583
583
-
id: lexiconId,
584
584
-
defs: {},
585
585
-
};
586
586
-
this.lexicons.set(lexiconId, lexicon);
587
587
-
}
588
588
-
589
589
-
// Add the model as a definition
590
590
-
const defName = model.name.charAt(0).toLowerCase() + model.name.slice(1);
591
591
-
const modelDef = this.modelToLexiconObject(model);
592
592
-
593
593
-
// Check if this is a defs file (ends with .defs)
594
594
-
if (lexiconId.endsWith(".defs")) {
595
595
-
// For defs files, all models go directly into defs object
596
596
-
const description = getDoc(this.program, model);
597
597
-
if (description && !modelDef.description) {
598
598
-
modelDef.description = description;
599
599
-
}
600
600
-
lexicon.defs[defName] = modelDef;
601
601
-
} else {
602
602
-
// For non-defs files, treat the first model as the main record
603
603
-
if (Object.keys(lexicon.defs).length === 0) {
604
604
-
// Check if this is the lexicon schema special case
605
605
-
const key = lexiconId === "com.atproto.lexicon.schema" ? "nsid" : "tid";
606
606
-
607
607
-
const recordDef: any = {
608
608
-
type: "record",
609
609
-
key: key,
610
610
-
record: modelDef,
611
611
-
};
612
612
-
613
613
-
// Move description from record object to record def
614
614
-
const description = getDoc(this.program, model);
615
615
-
if (description) {
616
616
-
recordDef.description = description;
617
617
-
delete modelDef.description;
618
618
-
}
619
619
-
620
620
-
lexicon.defs.main = recordDef;
621
621
-
} else {
622
622
-
lexicon.defs[defName] = modelDef;
623
623
-
}
624
624
-
}
625
625
-
}
626
626
-
627
774
private modelToLexiconObject(
628
775
model: Model,
629
776
includeModelDescription: boolean = true,
···
658
805
if (hasNull) {
659
806
nullable.push(name);
660
807
const nonNullVariant = variants.find(
661
661
-
(v) => !(v.type.kind === "Intrinsic" && (v.type as any).name === "null"),
808
808
+
(v) =>
809
809
+
!(v.type.kind === "Intrinsic" && (v.type as any).name === "null"),
662
810
);
663
811
if (nonNullVariant) typeToProcess = nonNullVariant.type;
664
812
}
···
669
817
}
670
818
671
819
const obj: any = { type: "object" };
672
672
-
const description = includeModelDescription ? getDoc(this.program, model) : undefined;
820
820
+
const description = includeModelDescription
821
821
+
? getDoc(this.program, model)
822
822
+
: undefined;
673
823
if (description) obj.description = description;
674
824
if (required.length) obj.required = required;
675
825
if (nullable.length) obj.nullable = nullable;
···
685
835
const propDesc = prop ? getDoc(this.program, prop) : undefined;
686
836
687
837
switch (type.kind) {
688
688
-
case "Namespace": {
689
689
-
const mainModel = (type as any).models?.get("Main");
690
690
-
if (mainModel) {
691
691
-
const ref = this.getModelReference(mainModel);
692
692
-
if (ref) return this.addDescription({ type: "ref", ref }, propDesc);
693
693
-
}
838
838
+
case "Namespace":
839
839
+
return this.handleNamespaceType(type as any, propDesc);
840
840
+
case "Enum":
841
841
+
return this.handleEnumType(type as any, propDesc);
842
842
+
case "Boolean":
843
843
+
return this.handleBooleanType(type as any, propDesc);
844
844
+
case "Scalar":
845
845
+
return this.handleScalarType(type as Scalar, prop, propDesc);
846
846
+
case "Model":
847
847
+
return this.handleModelType(type as Model, prop, propDesc);
848
848
+
case "Union":
849
849
+
return this.handleUnionType(type as Union, prop, isDefining, propDesc);
850
850
+
case "Intrinsic":
851
851
+
return this.addDescription({ type: "unknown" }, propDesc);
852
852
+
default:
853
853
+
// Unhandled type kind
694
854
return null;
695
695
-
}
696
696
-
case "Enum": {
697
697
-
const members = Array.from((type as any).members?.values?.() || []);
698
698
-
const values = members.map((m: any) => m.value);
699
699
-
const firstValue = values[0];
855
855
+
}
856
856
+
}
700
857
701
701
-
if (typeof firstValue === "string") {
702
702
-
return this.addDescription({ type: "string", enum: values }, propDesc);
703
703
-
} else if (typeof firstValue === "number" && Number.isInteger(firstValue)) {
704
704
-
return this.addDescription({ type: "integer", enum: values }, propDesc);
705
705
-
}
706
706
-
return null;
858
858
+
private handleNamespaceType(
859
859
+
ns: any,
860
860
+
propDesc?: string,
861
861
+
): LexiconDefinition | null {
862
862
+
const mainModel = ns.models?.get("Main");
863
863
+
if (mainModel) {
864
864
+
const ref = this.getModelReference(mainModel);
865
865
+
if (ref) {
866
866
+
return this.addDescription({ type: "ref", ref }, propDesc);
707
867
}
708
708
-
case "Boolean": {
709
709
-
return this.addDescription({
710
710
-
type: "boolean",
711
711
-
const: (type as any).value
712
712
-
}, propDesc);
713
713
-
}
714
714
-
case "Scalar":
715
715
-
const scalar = type as Scalar;
716
716
-
const primitive = this.scalarToLexiconPrimitive(scalar, prop);
717
717
-
if (!primitive) return null;
868
868
+
}
869
869
+
return null;
870
870
+
}
718
871
719
719
-
if (propDesc) {
720
720
-
primitive.description = propDesc;
721
721
-
} else if (scalar.baseScalar && scalar.namespace?.name !== "TypeSpec") {
722
722
-
const FORMAT_SCALARS = new Set([
723
723
-
"datetime", "did", "handle", "atUri", "cid", "tid", "nsid",
724
724
-
"recordKey", "uri", "language", "atIdentifier", "bytes",
725
725
-
"utcDateTime", "offsetDateTime", "plainDate", "plainTime",
726
726
-
]);
727
727
-
if (!FORMAT_SCALARS.has(scalar.name)) {
728
728
-
const scalarDesc = getDoc(this.program, scalar);
729
729
-
if (scalarDesc) primitive.description = scalarDesc;
730
730
-
}
731
731
-
}
732
732
-
return primitive;
733
733
-
case "Model":
734
734
-
const model = type as Model;
872
872
+
private handleEnumType(
873
873
+
enumType: any,
874
874
+
propDesc?: string,
875
875
+
): LexiconDefinition | null {
876
876
+
const members = Array.from(enumType.members?.values?.() || []);
735
877
736
736
-
const isBlobModel =
737
737
-
isBlob(this.program, model) ||
738
738
-
(isTemplateInstance(model) && model.templateNode && isBlob(this.program, model.templateNode as any)) ||
739
739
-
(model.baseModel && isBlob(this.program, model.baseModel));
878
878
+
if (members.length === 0) {
879
879
+
// TODO: Should we error on empty enum?
880
880
+
return null;
881
881
+
}
740
882
741
741
-
if (isBlobModel) {
742
742
-
return this.addDescription(this.createBlobDef(model), propDesc);
743
743
-
}
883
883
+
const values = members.map((m: any) => m.value);
884
884
+
const firstValue = values[0];
744
885
745
745
-
const isClosedModel =
746
746
-
model.name === "Closed" ||
747
747
-
(model.node && (model.node as any).symbol?.name === "Closed") ||
748
748
-
(isTemplateInstance(model) && model.node && (model.node as any).symbol?.name === "Closed");
886
886
+
if (typeof firstValue === "string") {
887
887
+
return this.addDescription({ type: "string", enum: values }, propDesc);
888
888
+
} else if (typeof firstValue === "number" && Number.isInteger(firstValue)) {
889
889
+
return this.addDescription({ type: "integer", enum: values }, propDesc);
890
890
+
}
749
891
750
750
-
if (isClosedModel && isTemplateInstance(model)) {
751
751
-
const unionArg = model.templateMapper?.args?.[0];
752
752
-
if (unionArg && isType(unionArg) && unionArg.kind === "Union") {
753
753
-
const unionDef = this.typeToLexiconDefinition(unionArg, prop);
754
754
-
if (unionDef && unionDef.type === "union") {
755
755
-
(unionDef as LexiconUnion).closed = true;
756
756
-
return unionDef;
757
757
-
}
758
758
-
}
759
759
-
}
892
892
+
// TODO: Handle mixed-type enums or float enums
893
893
+
return null;
894
894
+
}
760
895
761
761
-
const modelRef = this.getModelReference(model);
762
762
-
if (modelRef) {
763
763
-
return this.addDescription({ type: "ref", ref: modelRef }, propDesc);
764
764
-
}
896
896
+
private handleBooleanType(
897
897
+
boolType: any,
898
898
+
propDesc?: string,
899
899
+
): LexiconDefinition {
900
900
+
return this.addDescription(
901
901
+
{ type: "boolean", const: boolType.value },
902
902
+
propDesc,
903
903
+
);
904
904
+
}
765
905
766
766
-
if (isArrayModelType(this.program, model)) {
767
767
-
return this.addDescription(this.modelToLexiconArray(model, prop), propDesc);
768
768
-
}
906
906
+
private handleScalarType(
907
907
+
scalar: Scalar,
908
908
+
prop?: ModelProperty,
909
909
+
propDesc?: string,
910
910
+
): LexiconDefinition | null {
911
911
+
const primitive = this.scalarToLexiconPrimitive(scalar, prop);
912
912
+
if (!primitive) return null;
769
913
770
770
-
return this.addDescription(this.modelToLexiconObject(model), propDesc);
771
771
-
case "Union":
772
772
-
const unionType = type as Union;
914
914
+
if (propDesc) {
915
915
+
primitive.description = propDesc;
916
916
+
} else if (scalar.baseScalar && scalar.namespace?.name !== "TypeSpec") {
917
917
+
// For custom scalars that extend base types, inherit description if not a format scalar
918
918
+
if (!FORMAT_SCALARS.has(scalar.name)) {
919
919
+
const scalarDesc = getDoc(this.program, scalar);
920
920
+
if (scalarDesc) primitive.description = scalarDesc;
921
921
+
}
922
922
+
}
923
923
+
return primitive;
924
924
+
}
773
925
774
774
-
if (!isDefining) {
775
775
-
const unionRef = this.getUnionReference(unionType);
776
776
-
if (unionRef) {
777
777
-
return this.addDescription({ type: "ref", ref: unionRef }, propDesc);
778
778
-
}
926
926
+
private handleModelType(
927
927
+
model: Model,
928
928
+
prop?: ModelProperty,
929
929
+
propDesc?: string,
930
930
+
): LexiconDefinition | null {
931
931
+
// 1. Check for Blob type
932
932
+
if (this.isBlob(model)) {
933
933
+
return this.addDescription(this.createBlobDef(model), propDesc);
934
934
+
}
935
935
+
936
936
+
// 2. Check for Closed<Union> template
937
937
+
if (this.isClosedUnionTemplate(model)) {
938
938
+
const unionArg = model.templateMapper?.args?.[0];
939
939
+
if (unionArg && isType(unionArg) && unionArg.kind === "Union") {
940
940
+
const unionDef = this.typeToLexiconDefinition(unionArg, prop);
941
941
+
if (unionDef && unionDef.type === "union") {
942
942
+
(unionDef as LexiconUnion).closed = true;
943
943
+
return unionDef;
779
944
}
945
945
+
}
946
946
+
// TODO: Handle Closed<> with non-union argument
947
947
+
return null;
948
948
+
}
780
949
781
781
-
return this.processUnion(unionType, prop);
782
782
-
case "Intrinsic":
783
783
-
return this.addDescription({ type: "unknown" }, propDesc);
784
784
-
default:
950
950
+
// 3. Check for model reference (named models from other namespaces)
951
951
+
const modelRef = this.getModelReference(model);
952
952
+
if (modelRef) {
953
953
+
return this.addDescription({ type: "ref", ref: modelRef }, propDesc);
954
954
+
}
955
955
+
956
956
+
// 4. Check for array type
957
957
+
if (isArrayModelType(this.program, model)) {
958
958
+
const arrayDef = this.modelToLexiconArray(model, prop);
959
959
+
if (!arrayDef) {
960
960
+
// TODO: Handle array conversion failure
785
961
return null;
962
962
+
}
963
963
+
return this.addDescription(arrayDef, propDesc);
786
964
}
965
965
+
966
966
+
// 5. Inline object
967
967
+
return this.addDescription(this.modelToLexiconObject(model), propDesc);
968
968
+
}
969
969
+
970
970
+
private handleUnionType(
971
971
+
unionType: Union,
972
972
+
prop?: ModelProperty,
973
973
+
isDefining?: boolean,
974
974
+
propDesc?: string,
975
975
+
): LexiconDefinition | null {
976
976
+
// Check if this is a named union that should be referenced
977
977
+
if (!isDefining) {
978
978
+
const unionRef = this.getUnionReference(unionType);
979
979
+
if (unionRef) {
980
980
+
return this.addDescription({ type: "ref", ref: unionRef }, propDesc);
981
981
+
}
982
982
+
}
983
983
+
984
984
+
return this.processUnion(unionType, prop);
787
985
}
788
986
789
987
private scalarToLexiconPrimitive(
790
988
scalar: Scalar,
791
989
prop?: ModelProperty,
792
990
): LexiconDefinition | null {
793
793
-
const FORMAT_MAP: Record<string, string> = {
794
794
-
did: "did", handle: "handle", atUri: "at-uri", datetime: "datetime",
795
795
-
cid: "cid", tid: "tid", nsid: "nsid", recordKey: "record-key",
796
796
-
uri: "uri", language: "language", atIdentifier: "at-identifier",
797
797
-
utcDateTime: "datetime", offsetDateTime: "datetime",
798
798
-
plainDate: "datetime", plainTime: "datetime"
799
799
-
};
800
800
-
991
991
+
// Special case: bytes type can be either blob or bytes depending on decorators
801
992
if (scalar.name === "bytes") {
802
802
-
if (prop) {
803
803
-
const accept = getBlobAccept(this.program, prop);
804
804
-
const maxSize = getBlobMaxSize(this.program, prop);
805
805
-
if (accept || maxSize !== undefined) {
806
806
-
const blobDef: LexiconBlob = { type: "blob" };
807
807
-
if (accept) blobDef.accept = accept;
808
808
-
if (maxSize !== undefined) blobDef.maxSize = maxSize;
809
809
-
return blobDef;
810
810
-
}
811
811
-
}
812
812
-
return { type: "bytes" };
993
993
+
return this.handleBytesScalar(prop);
813
994
}
814
995
815
815
-
const primitive: any = { type: "string" };
996
996
+
// Determine base primitive type
997
997
+
const primitive: any = this.getBasePrimitiveType(scalar);
816
998
817
817
-
if (scalar.name === "boolean") primitive.type = "boolean";
818
818
-
else if (["integer", "int32", "int64", "int16", "int8"].includes(scalar.name)) primitive.type = "integer";
819
819
-
else if (["float32", "float64"].includes(scalar.name)) primitive.type = "number";
820
820
-
821
821
-
const format = FORMAT_MAP[scalar.name] || getLexFormat(this.program, prop || scalar);
999
999
+
// Apply format if applicable
1000
1000
+
const format =
1001
1001
+
FORMAT_MAP[scalar.name] || getLexFormat(this.program, prop || scalar);
822
1002
if (format) primitive.format = format;
823
1003
824
824
-
const target = prop || scalar;
1004
1004
+
// Apply constraints
1005
1005
+
this.applyStringConstraints(primitive, prop || scalar);
1006
1006
+
this.applyNumericConstraints(primitive, prop);
1007
1007
+
1008
1008
+
// Apply property-specific metadata
1009
1009
+
if (prop) {
1010
1010
+
this.applyPropertyMetadata(primitive, prop);
1011
1011
+
}
1012
1012
+
1013
1013
+
return primitive;
1014
1014
+
}
1015
1015
+
1016
1016
+
private handleBytesScalar(prop?: ModelProperty): LexiconDefinition {
1017
1017
+
if (prop) {
1018
1018
+
const accept = getBlobAccept(this.program, prop);
1019
1019
+
const maxSize = getBlobMaxSize(this.program, prop);
1020
1020
+
if (accept || maxSize !== undefined) {
1021
1021
+
const blobDef: LexiconBlob = { type: "blob" };
1022
1022
+
if (accept) blobDef.accept = accept;
1023
1023
+
if (maxSize !== undefined) blobDef.maxSize = maxSize;
1024
1024
+
return blobDef;
1025
1025
+
}
1026
1026
+
}
1027
1027
+
return { type: "bytes" };
1028
1028
+
}
1029
1029
+
1030
1030
+
private getBasePrimitiveType(scalar: Scalar): any {
1031
1031
+
if (scalar.name === "boolean") {
1032
1032
+
return { type: "boolean" };
1033
1033
+
} else if (
1034
1034
+
["integer", "int32", "int64", "int16", "int8"].includes(scalar.name)
1035
1035
+
) {
1036
1036
+
return { type: "integer" };
1037
1037
+
} else if (["float32", "float64"].includes(scalar.name)) {
1038
1038
+
return { type: "number" };
1039
1039
+
}
1040
1040
+
return { type: "string" };
1041
1041
+
}
1042
1042
+
1043
1043
+
private applyStringConstraints(
1044
1044
+
primitive: any,
1045
1045
+
target: Scalar | ModelProperty,
1046
1046
+
) {
825
1047
const maxLength = getMaxLength(this.program, target);
826
1048
if (maxLength !== undefined) primitive.maxLength = maxLength;
1049
1049
+
827
1050
const minLength = getMinLength(this.program, target);
828
1051
if (minLength !== undefined) primitive.minLength = minLength;
1052
1052
+
829
1053
const maxGraphemes = getMaxGraphemes(this.program, target);
830
1054
if (maxGraphemes !== undefined) primitive.maxGraphemes = maxGraphemes;
1055
1055
+
831
1056
const minGraphemes = getMinGraphemes(this.program, target);
832
1057
if (minGraphemes !== undefined) primitive.minGraphemes = minGraphemes;
1058
1058
+
}
833
1059
834
834
-
if (prop) {
835
835
-
const constValue = getLexConst(this.program, prop);
836
836
-
if (constValue !== undefined &&
837
837
-
(primitive.type === "boolean" && typeof constValue === "boolean" ||
838
838
-
primitive.type === "string" && typeof constValue === "string" ||
839
839
-
primitive.type === "integer" && typeof constValue === "number")) {
840
840
-
primitive.const = constValue;
841
841
-
}
1060
1060
+
private applyNumericConstraints(primitive: any, prop?: ModelProperty) {
1061
1061
+
if (
1062
1062
+
!prop ||
1063
1063
+
(primitive.type !== "integer" && primitive.type !== "number")
1064
1064
+
) {
1065
1065
+
return;
1066
1066
+
}
1067
1067
+
1068
1068
+
const minValue = getMinValue(this.program, prop);
1069
1069
+
if (minValue !== undefined) primitive.minimum = minValue;
1070
1070
+
1071
1071
+
const maxValue = getMaxValue(this.program, prop);
1072
1072
+
if (maxValue !== undefined) primitive.maximum = maxValue;
1073
1073
+
}
842
1074
843
843
-
const defaultValue = (prop as any).default?.value;
844
844
-
if (defaultValue !== undefined &&
845
845
-
(primitive.type === "string" && typeof defaultValue === "string" ||
846
846
-
primitive.type === "integer" && typeof defaultValue === "number" ||
847
847
-
primitive.type === "boolean" && typeof defaultValue === "boolean")) {
848
848
-
primitive.default = defaultValue;
849
849
-
}
1075
1075
+
private applyPropertyMetadata(primitive: any, prop: ModelProperty) {
1076
1076
+
// Apply const value
1077
1077
+
const constValue = getLexConst(this.program, prop);
1078
1078
+
if (
1079
1079
+
constValue !== undefined &&
1080
1080
+
this.isValidConstForType(primitive.type, constValue)
1081
1081
+
) {
1082
1082
+
primitive.const = constValue;
1083
1083
+
}
850
1084
851
851
-
if (primitive.type === "integer" || primitive.type === "number") {
852
852
-
const minValue = getMinValue(this.program, prop);
853
853
-
if (minValue !== undefined) primitive.minimum = minValue;
854
854
-
const maxValue = getMaxValue(this.program, prop);
855
855
-
if (maxValue !== undefined) primitive.maximum = maxValue;
856
856
-
}
1085
1085
+
// Apply default value
1086
1086
+
const defaultValue = (prop as any).default?.value;
1087
1087
+
if (
1088
1088
+
defaultValue !== undefined &&
1089
1089
+
this.isValidDefaultForType(primitive.type, defaultValue)
1090
1090
+
) {
1091
1091
+
primitive.default = defaultValue;
857
1092
}
1093
1093
+
}
858
1094
859
859
-
return primitive;
1095
1095
+
private isValidConstForType(primitiveType: string, constValue: any): boolean {
1096
1096
+
return (
1097
1097
+
(primitiveType === "boolean" && typeof constValue === "boolean") ||
1098
1098
+
(primitiveType === "string" && typeof constValue === "string") ||
1099
1099
+
(primitiveType === "integer" && typeof constValue === "number")
1100
1100
+
);
1101
1101
+
}
1102
1102
+
1103
1103
+
private isValidDefaultForType(
1104
1104
+
primitiveType: string,
1105
1105
+
defaultValue: any,
1106
1106
+
): boolean {
1107
1107
+
return (
1108
1108
+
(primitiveType === "string" && typeof defaultValue === "string") ||
1109
1109
+
(primitiveType === "integer" && typeof defaultValue === "number") ||
1110
1110
+
(primitiveType === "boolean" && typeof defaultValue === "boolean")
1111
1111
+
);
860
1112
}
861
1113
862
1114
private getModelReference(model: Model): string | null {
863
863
-
if (!model.name || !model.namespace || model.namespace.name === "TypeSpec") return null;
1115
1115
+
if (!model.name || !model.namespace || model.namespace.name === "TypeSpec")
1116
1116
+
return null;
864
1117
865
1118
const namespaceName = getNamespaceFullName(model.namespace);
866
1119
if (!namespaceName) return null;
867
1120
868
1121
const defName = model.name.charAt(0).toLowerCase() + model.name.slice(1);
869
1122
870
870
-
if (this.currentLexiconId === namespaceName || this.currentLexiconId === `${namespaceName}.defs`) {
1123
1123
+
if (
1124
1124
+
this.currentLexiconId === namespaceName ||
1125
1125
+
this.currentLexiconId === `${namespaceName}.defs`
1126
1126
+
) {
871
1127
return `#${defName}`;
872
1128
}
873
1129
874
874
-
return model.name === "Main" ? namespaceName : `${namespaceName}#${defName}`;
1130
1130
+
return model.name === "Main"
1131
1131
+
? namespaceName
1132
1132
+
: `${namespaceName}#${defName}`;
875
1133
}
876
1134
877
1135
private getUnionReference(union: Union): string | null {
···
884
1142
885
1143
const defName = unionName.charAt(0).toLowerCase() + unionName.slice(1);
886
1144
887
887
-
if (this.currentLexiconId === namespaceName || this.currentLexiconId === `${namespaceName}.defs`) {
1145
1145
+
if (
1146
1146
+
this.currentLexiconId === namespaceName ||
1147
1147
+
this.currentLexiconId === `${namespaceName}.defs`
1148
1148
+
) {
888
1149
return `#${defName}`;
889
1150
}
890
1151
···
917
1178
return null;
918
1179
}
919
1180
920
920
-
private getModelLexiconId(model: Model): string | null {
921
921
-
if (!model.namespace) return null;
922
922
-
923
923
-
const namespaceName = getNamespaceFullName(model.namespace);
924
924
-
if (!namespaceName) return null;
925
925
-
926
926
-
if (model.name === "Main") return namespaceName;
927
927
-
928
928
-
return `${namespaceName}.${model.name.charAt(0).toLowerCase() + model.name.slice(1)}`;
929
929
-
}
930
930
-
931
1181
private getLexiconPath(lexiconId: string): string {
932
1182
const parts = lexiconId.split(".");
933
933
-
return join(this.options.outputDir, ...parts.slice(0, -1), parts[parts.length - 1] + ".json");
1183
1183
+
return join(
1184
1184
+
this.options.outputDir,
1185
1185
+
...parts.slice(0, -1),
1186
1186
+
parts[parts.length - 1] + ".json",
1187
1187
+
);
934
1188
}
935
1189
936
1190
private async writeFile(filePath: string, content: string) {
+5
packages/example/src/lexicons.ts
···
69
69
},
70
70
},
71
71
},
72
72
+
notificationType: {
73
73
+
type: 'string',
74
74
+
knownValues: ['like', 'repost', 'follow', 'mention', 'reply'],
75
75
+
description: 'Type of notification',
76
76
+
},
72
77
},
73
78
},
74
79
AppExampleFollow: {
+9
packages/example/src/types/app/example/defs.ts
···
68
68
export function validateEntity<V>(v: V) {
69
69
return validate<Entity & V>(v, id, hashEntity)
70
70
}
71
71
+
72
72
+
/** Type of notification */
73
73
+
export type NotificationType =
74
74
+
| 'like'
75
75
+
| 'repost'
76
76
+
| 'follow'
77
77
+
| 'mention'
78
78
+
| 'reply'
79
79
+
| (string & {})