An experimental TypeSpec syntax for Lexicon
56
fork

Configure Feed

Select the types of activity you want to include in your feed.

more

+94 -19
+18 -11
typelex-emitter/lib/decorators.tsp
··· 11 11 * - "handle": AT Protocol handle 12 12 * - "uri": Generic URI 13 13 * - "language": ISO language code 14 - * 15 - * @example 16 - * ```typespec 17 - * model Post { 18 - * @lexFormat("at-uri") 19 - * uri: string; 20 - * 21 - * @lexFormat("cid") 22 - * cid: string; 23 - * } 24 - * ``` 25 14 */ 26 15 extern dec lexFormat(target: unknown, format: valueof string); 16 + 17 + /** 18 + * Specifies the maximum number of graphemes (user-perceived characters) allowed. 19 + * Used alongside maxLength for proper Unicode text handling. 20 + */ 21 + extern dec maxGraphemes(target: unknown, value: valueof int32); 22 + 23 + /** 24 + * Specifies the minimum number of graphemes allowed. 25 + */ 26 + extern dec minGraphemes(target: unknown, value: valueof int32); 27 + 28 + /** 29 + * Specifies known/suggested values for a string field in lexicon format. 30 + * The field can accept other values (open enum), unlike TypeSpec's @knownValues. 31 + * Maps to lexicon's "knownValues" field. 32 + */ 33 + extern dec lexKnownValues(target: unknown, values: valueof string[]);
+2 -2
typelex-emitter/package.json
··· 6 6 "type": "module", 7 7 "scripts": { 8 8 "build": "tsc", 9 - "test": "npm run build && vitest", 10 - "test:ci": "npm run build && vitest run", 9 + "test": "npm run build && vitest run", 10 + "test:watch": "npm run build && vitest", 11 11 "clean": "rm -rf dist", 12 12 "watch": "tsc --watch" 13 13 },
+56 -2
typelex-emitter/src/decorators.ts
··· 1 1 import { DecoratorContext, Program, Type } from "@typespec/compiler"; 2 2 3 3 const formatKey = Symbol("lexFormat"); 4 + const maxGraphemesKey = Symbol("maxGraphemes"); 5 + const minGraphemesKey = Symbol("minGraphemes"); 6 + const knownValuesKey = Symbol("knownValues"); 4 7 5 8 /** 6 9 * @lexFormat decorator for lexicon-specific string formats 7 - * Used for formats like "at-uri", "cid", "did", "datetime", etc. 8 10 */ 9 11 export function $lexFormat(context: DecoratorContext, target: Type, format: Type) { 10 - // Extract the value from the Type if it's a string literal 11 12 const formatValue = (format as any).kind === "String" ? (format as any).value : format; 12 13 context.program.stateMap(formatKey).set(target, formatValue); 13 14 } ··· 15 16 export function getLexFormat(program: Program, target: Type): string | undefined { 16 17 return program.stateMap(formatKey).get(target); 17 18 } 19 + 20 + /** 21 + * @maxGraphemes decorator for maximum grapheme count 22 + */ 23 + export function $maxGraphemes(context: DecoratorContext, target: Type, value: Type) { 24 + const numValue = (value as any).kind === "Number" || (value as any).kind === "Numeric" ? (value as any).value : value; 25 + context.program.stateMap(maxGraphemesKey).set(target, Number(numValue)); 26 + } 27 + 28 + export function getMaxGraphemes(program: Program, target: Type): number | undefined { 29 + return program.stateMap(maxGraphemesKey).get(target); 30 + } 31 + 32 + /** 33 + * @minGraphemes decorator for minimum grapheme count 34 + */ 35 + export function $minGraphemes(context: DecoratorContext, target: Type, value: Type) { 36 + const numValue = (value as any).kind === "Number" || (value as any).kind === "Numeric" ? (value as any).value : value; 37 + context.program.stateMap(minGraphemesKey).set(target, Number(numValue)); 38 + } 39 + 40 + export function getMinGraphemes(program: Program, target: Type): number | undefined { 41 + return program.stateMap(minGraphemesKey).get(target); 42 + } 43 + 44 + /** 45 + * @lexKnownValues decorator for open-ended enum values in lexicon format 46 + */ 47 + export function $lexKnownValues(context: DecoratorContext, target: Type, values: Type) { 48 + // Handle array of string literals - valueof string[] passes an object with values array 49 + const valuesAny = values as any; 50 + 51 + if (Array.isArray(valuesAny)) { 52 + // Direct array of strings 53 + context.program.stateMap(knownValuesKey).set(target, valuesAny); 54 + } else if (valuesAny.kind === "Tuple" && valuesAny.values) { 55 + // Tuple type with values 56 + const stringValues = valuesAny.values.map((v: any) => v.value || v); 57 + context.program.stateMap(knownValuesKey).set(target, stringValues); 58 + } else if (valuesAny.values && Array.isArray(valuesAny.values)) { 59 + // Object containing array in values property 60 + const stringValues = valuesAny.values.map((v: any) => { 61 + if (typeof v === 'string') return v; 62 + if (v.value) return v.value; 63 + return String(v); 64 + }); 65 + context.program.stateMap(knownValuesKey).set(target, stringValues); 66 + } 67 + } 68 + 69 + export function getLexKnownValues(program: Program, target: Type): string[] | undefined { 70 + return program.stateMap(knownValuesKey).get(target); 71 + }
+17 -3
typelex-emitter/src/emitter.ts
··· 21 21 LexiconRef, 22 22 LexiconUnion, 23 23 } from "./types.js"; 24 - import { getLexFormat } from "./decorators.js"; 24 + import { getLexFormat, getMaxGraphemes, getMinGraphemes, getLexKnownValues } from "./decorators.js"; 25 25 26 26 export interface EmitterOptions { 27 27 outputDir: string; ··· 343 343 }; 344 344 } 345 345 346 - // Check for @lexFormat decorator on the property 346 + // Check for decorators on the property 347 347 if (prop) { 348 348 const format = getLexFormat(this.program, prop); 349 349 if (format) { 350 350 primitive.format = format; 351 351 } 352 352 353 - // Check for @maxLength decorator 354 353 const maxLength = getMaxLength(this.program, prop); 355 354 if (maxLength !== undefined) { 356 355 primitive.maxLength = maxLength; 356 + } 357 + 358 + const maxGraphemes = getMaxGraphemes(this.program, prop); 359 + if (maxGraphemes !== undefined) { 360 + primitive.maxGraphemes = maxGraphemes; 361 + } 362 + 363 + const minGraphemes = getMinGraphemes(this.program, prop); 364 + if (minGraphemes !== undefined) { 365 + primitive.minGraphemes = minGraphemes; 366 + } 367 + 368 + const knownValues = getLexKnownValues(this.program, prop); 369 + if (knownValues) { 370 + primitive.knownValues = knownValues; 357 371 } 358 372 } 359 373
+1 -1
typelex-emitter/src/index.ts
··· 14 14 } 15 15 16 16 // Export decorators 17 - export { $lexFormat } from "./decorators.js"; 17 + export { $lexFormat, $maxGraphemes, $minGraphemes, $lexKnownValues } from "./decorators.js";