An experimental TypeSpec syntax for Lexicon

wip

+11 -93
+6 -22
IMPROVEMENTS.md
··· 42 42 ## High Value Improvements 43 43 44 44 ### 5. Provide Pre-defined Format Scalars 45 - **Current:** Only `@lexFormat("did")` - not type-safe 46 - **Change:** Add pre-defined scalars in lib/decorators.tsp: 47 - ```typespec 48 - scalar did extends string; 49 - scalar handle extends string; 50 - scalar atUri extends string; 51 - scalar datetime extends string; 52 - scalar cid extends string; 53 - scalar tid extends string; 54 - scalar nsid extends string; 55 - scalar recordKey extends string; 56 - scalar uri extends string; 57 - scalar language extends string; 58 - ``` 45 + **Status:** ✅ DONE 46 + **Implementation:** Pre-defined format scalars added (did, handle, atUri, datetime, cid, cidLink, etc.) 59 47 **Files:** 60 - - `lib/decorators.tsp` - Add scalar definitions 61 - - `src/emitter.ts` - Detect these scalars and map to format automatically 62 - **Test Impact:** Can start using in new tests 63 - **Note:** Keep `@lexFormat` for backward compatibility and flexibility 48 + - `lib/main.tsp` - Scalar definitions 49 + - `src/emitter.ts` - Detects and maps to format automatically 64 50 65 51 ### 6. Add Const Support for String/Integer 66 52 **Current:** Only `@lexConst` for boolean ··· 75 61 ## Nice to Have (Lower Priority) 76 62 77 63 ### 7. Native DateTime Type Mapping 78 - **Current:** `@lexFormat("datetime")` on string 79 - **Enhancement:** Optionally map `utcDateTime` → `format: "datetime"` 80 - **Note:** Lexicon only has one datetime format, TypeSpec has multiple 81 - **Verdict:** Lower priority, explicit `@lexFormat` is clearer 64 + **Status:** ✅ DONE (via `datetime` scalar) 65 + **Implementation:** Use `datetime` scalar instead of `@lexFormat("datetime")` 82 66 83 67 ### 8. Token Type Support 84 68 **When needed:** Implement token pattern
-27
SYNTAX.md
··· 427 427 428 428 **Valid key types:** `"tid"`, `"self"`, `"nsid"` 429 429 430 - ### Alternative: @lexFormat decorator 431 - 432 - **Pattern:** Use `@lexFormat` decorator instead of format scalars 433 - 434 - **TypeSpec:** 435 - ```typespec 436 - model Example { 437 - @lexFormat("at-uri") 438 - @required 439 - uri: string; 440 - 441 - @lexFormat("datetime") 442 - @required 443 - createdAt: string; 444 - } 445 - ``` 446 - 447 - **JSON:** 448 - ```json 449 - { 450 - "uri": { "type": "string", "format": "at-uri" }, 451 - "createdAt": { "type": "string", "format": "datetime" } 452 - } 453 - ``` 454 - 455 - **Note:** Using predefined format scalars (`atUri`, `datetime`, etc.) is preferred over `@lexFormat` for better type safety and consistency. 456 - 457 430 --- 458 431 459 432 ## Arrays
-17
packages/emitter/lib/decorators.tsp
··· 1 1 import "../dist/tsp-index.js"; 2 2 3 3 /** 4 - * Specifies the lexicon format of a string field. 5 - * 6 - * Common formats: 7 - * - "at-uri": AT Protocol URI 8 - * - "cid": Content identifier 9 - * - "did": Decentralized identifier 10 - * - "datetime": ISO 8601 datetime 11 - * - "handle": AT Protocol handle 12 - * - "uri": Generic URI 13 - * - "language": ISO language code 14 - * 15 - * NOTE: Consider using the predefined format scalars (did, handle, atUri, etc.) 16 - * instead of this decorator for better type safety. 17 - */ 18 - extern dec lexFormat(target: unknown, format: valueof string); 19 - 20 - /** 21 4 * Specifies the maximum number of graphemes (user-perceived characters) allowed. 22 5 * Used alongside maxLength for proper Unicode text handling. 23 6 */
-13
packages/emitter/src/decorators.ts
··· 1 1 import { DecoratorContext, Program, Type } from "@typespec/compiler"; 2 2 import { getDoc } from "@typespec/compiler"; 3 3 4 - const formatKey = Symbol("lexFormat"); 5 4 const maxGraphemesKey = Symbol("maxGraphemes"); 6 5 const minGraphemesKey = Symbol("minGraphemes"); 7 6 const constKey = Symbol("const"); ··· 42 41 43 42 export function getEncoding(program: Program, target: Type): string | undefined { 44 43 return program.stateMap(encodingKey).get(target); 45 - } 46 - 47 - /** 48 - * @lexFormat decorator for lexicon-specific string formats 49 - */ 50 - export function $lexFormat(context: DecoratorContext, target: Type, format: Type) { 51 - const formatValue = (format as any).kind === "String" ? (format as any).value : format; 52 - context.program.stateMap(formatKey).set(target, formatValue); 53 - } 54 - 55 - export function getLexFormat(program: Program, target: Type): string | undefined { 56 - return program.stateMap(formatKey).get(target); 57 44 } 58 45 59 46 /**
+1 -3
packages/emitter/src/emitter.ts
··· 30 30 LexiconBytes, 31 31 } from "./types.js"; 32 32 import { 33 - getLexFormat, 34 33 getMaxGraphemes, 35 34 getMinGraphemes, 36 35 getLexConst, ··· 978 977 const primitive: any = this.getBasePrimitiveType(scalar); 979 978 980 979 // Apply format if applicable 981 - const format = 982 - FORMAT_MAP[scalar.name] || getLexFormat(this.program, prop || scalar); 980 + const format = FORMAT_MAP[scalar.name]; 983 981 if (format) primitive.format = format; 984 982 985 983 // Apply constraints
-1
packages/emitter/src/index.ts
··· 18 18 19 19 // Export decorators 20 20 export { 21 - $lexFormat, 22 21 $maxGraphemes, 23 22 $minGraphemes, 24 23 $lexConst,
-2
packages/emitter/src/tsp-index.ts
··· 1 1 import { 2 - $lexFormat, 3 2 $maxGraphemes, 4 3 $minGraphemes, 5 4 $lexConst, ··· 20 19 /** @internal */ 21 20 export const $decorators = { 22 21 "": { 23 - lexFormat: $lexFormat, 24 22 maxGraphemes: $maxGraphemes, 25 23 minGraphemes: $minGraphemes, 26 24 lexConst: $lexConst,
+4 -8
packages/example/tlex/main.tsp
··· 68 68 @required 69 69 text: string; 70 70 71 - @lexFormat("datetime") 72 71 @doc("Creation timestamp") 73 72 @required 74 - createdAt: string; 73 + createdAt: datetime; 75 74 76 75 @doc("Languages the post is written in") 77 76 langs?: string[]; ··· 92 91 @required 93 92 subject: string; 94 93 95 - @lexFormat("datetime") 96 94 @doc("When the follow was created") 97 95 @required 98 - createdAt: string; 96 + createdAt: datetime; 99 97 } 100 98 } 101 99 ··· 107 105 @required 108 106 subject: app.example.defs.PostRef; 109 107 110 - @lexFormat("datetime") 111 108 @doc("When the like was created") 112 109 @required 113 - createdAt: string; 110 + createdAt: datetime; 114 111 } 115 112 } 116 113 ··· 122 119 @required 123 120 subject: app.example.defs.PostRef; 124 121 125 - @lexFormat("datetime") 126 122 @doc("When the repost was created") 127 123 @required 128 - createdAt: string; 124 + createdAt: datetime; 129 125 } 130 126 } 131 127