An experimental TypeSpec syntax for Lexicon
56
fork

Configure Feed

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

wip

+523 -40
+26
packages/emitter/lib/decorators.tsp
··· 12 12 extern dec minGraphemes(target: unknown, value: valueof int32); 13 13 14 14 /** 15 + * Specifies the minimum byte length for bytes type. 16 + */ 17 + extern dec minBytes(target: unknown, value: valueof int32); 18 + 19 + /** 15 20 * Marks a field as having a constant value. 16 21 * Maps to lexicon's "const" field. 17 22 * Supports boolean, string, and integer values. 18 23 */ 19 24 extern dec lexConst(target: unknown, value: unknown); 25 + 26 + /** 27 + * Specifies an enum of allowed values for a field. 28 + * Maps to lexicon's "enum" field. 29 + * Supports boolean, string, and integer values. 30 + * 31 + * @param values - Array of allowed values 32 + * 33 + * @example Integer enum 34 + * ```typespec 35 + * @lexEnum([1, 2, 3, 5, 8, 13]) 36 + * fibNumber?: integer; 37 + * ``` 38 + * 39 + * @example Boolean enum 40 + * ```typespec 41 + * @lexEnum([true]) 42 + * mustBeTrue?: boolean; 43 + * ``` 44 + */ 45 + extern dec lexEnum(target: unknown, ...values: unknown[]); 20 46 21 47 /** 22 48 * Marks a model as a record type with a specific key type.
+128 -21
packages/emitter/src/decorators.ts
··· 4 4 const maxGraphemesKey = Symbol("maxGraphemes"); 5 5 const minGraphemesKey = Symbol("minGraphemes"); 6 6 const constKey = Symbol("const"); 7 + const enumKey = Symbol("enum"); 7 8 const recordKey = Symbol("record"); 8 9 const blobKey = Symbol("blob"); 9 10 const requiredKey = Symbol("required"); ··· 16 17 const errorModelKey = Symbol("errorModel"); 17 18 const encodingKey = Symbol("encoding"); 18 19 const inlineKey = Symbol("inline"); 19 - const bytesMaxLengthKey = Symbol("bytesMaxLength"); 20 + const maxBytesKey = Symbol("maxBytes"); 21 + const minBytesKey = Symbol("minBytes"); 20 22 21 23 /** 22 - * @bytesMaxLength decorator for maximum length of bytes type 24 + * @maxBytes decorator for maximum length of bytes type 23 25 */ 24 - export function $bytesMaxLength(context: DecoratorContext, target: Type, value: Type) { 25 - const numValue = (value as any).kind === "Number" || (value as any).kind === "Numeric" ? (value as any).value : value; 26 - context.program.stateMap(bytesMaxLengthKey).set(target, Number(numValue)); 26 + export function $maxBytes( 27 + context: DecoratorContext, 28 + target: Type, 29 + value: Type, 30 + ) { 31 + const numValue = 32 + (value as any).kind === "Number" || (value as any).kind === "Numeric" 33 + ? (value as any).value 34 + : value; 35 + context.program.stateMap(maxBytesKey).set(target, Number(numValue)); 36 + } 37 + 38 + export function getMaxBytes( 39 + program: Program, 40 + target: Type, 41 + ): number | undefined { 42 + return program.stateMap(maxBytesKey).get(target); 43 + } 44 + 45 + export function $minBytes( 46 + context: DecoratorContext, 47 + target: Type, 48 + value: Type, 49 + ) { 50 + const numValue = 51 + (value as any).kind === "Number" || (value as any).kind === "Numeric" 52 + ? (value as any).value 53 + : value; 54 + context.program.stateMap(minBytesKey).set(target, Number(numValue)); 27 55 } 28 56 29 - export function getBytesMaxLength(program: Program, target: Type): number | undefined { 30 - return program.stateMap(bytesMaxLengthKey).get(target); 57 + export function getMinBytes( 58 + program: Program, 59 + target: Type, 60 + ): number | undefined { 61 + return program.stateMap(minBytesKey).get(target); 31 62 } 32 63 33 64 /** 34 65 * @encoding decorator for custom output/input encoding 35 66 * When on model: input encoding; when on operation: output encoding 36 67 */ 37 - export function $encoding(context: DecoratorContext, target: Type, encoding: Type) { 38 - const encodingValue = (encoding as any).kind === "String" ? (encoding as any).value : encoding; 68 + export function $encoding( 69 + context: DecoratorContext, 70 + target: Type, 71 + encoding: Type, 72 + ) { 73 + const encodingValue = 74 + (encoding as any).kind === "String" ? (encoding as any).value : encoding; 39 75 context.program.stateMap(encodingKey).set(target, encodingValue); 40 76 } 41 77 42 - export function getEncoding(program: Program, target: Type): string | undefined { 78 + export function getEncoding( 79 + program: Program, 80 + target: Type, 81 + ): string | undefined { 43 82 return program.stateMap(encodingKey).get(target); 44 83 } 45 84 46 85 /** 47 86 * @maxGraphemes decorator for maximum grapheme count 48 87 */ 49 - export function $maxGraphemes(context: DecoratorContext, target: Type, value: Type) { 50 - const numValue = (value as any).kind === "Number" || (value as any).kind === "Numeric" ? (value as any).value : value; 88 + export function $maxGraphemes( 89 + context: DecoratorContext, 90 + target: Type, 91 + value: Type, 92 + ) { 93 + const numValue = 94 + (value as any).kind === "Number" || (value as any).kind === "Numeric" 95 + ? (value as any).value 96 + : value; 51 97 context.program.stateMap(maxGraphemesKey).set(target, Number(numValue)); 52 98 } 53 99 54 - export function getMaxGraphemes(program: Program, target: Type): number | undefined { 100 + export function getMaxGraphemes( 101 + program: Program, 102 + target: Type, 103 + ): number | undefined { 55 104 return program.stateMap(maxGraphemesKey).get(target); 56 105 } 57 106 58 107 /** 59 108 * @minGraphemes decorator for minimum grapheme count 60 109 */ 61 - export function $minGraphemes(context: DecoratorContext, target: Type, value: Type) { 62 - const numValue = (value as any).kind === "Number" || (value as any).kind === "Numeric" ? (value as any).value : value; 110 + export function $minGraphemes( 111 + context: DecoratorContext, 112 + target: Type, 113 + value: Type, 114 + ) { 115 + const numValue = 116 + (value as any).kind === "Number" || (value as any).kind === "Numeric" 117 + ? (value as any).value 118 + : value; 63 119 context.program.stateMap(minGraphemesKey).set(target, Number(numValue)); 64 120 } 65 121 66 - export function getMinGraphemes(program: Program, target: Type): number | undefined { 122 + export function getMinGraphemes( 123 + program: Program, 124 + target: Type, 125 + ): number | undefined { 67 126 return program.stateMap(minGraphemesKey).get(target); 68 127 } 69 128 70 129 /** 71 130 * @lexConst decorator for constant values (boolean, string, or integer) 72 131 */ 73 - export function $lexConst(context: DecoratorContext, target: Type, value: Type) { 132 + export function $lexConst( 133 + context: DecoratorContext, 134 + target: Type, 135 + value: Type, 136 + ) { 74 137 const valueAny = value as any; 75 138 76 139 // Handle different value types ··· 85 148 } 86 149 } 87 150 88 - export function getLexConst(program: Program, target: Type): boolean | string | number | undefined { 151 + export function getLexConst( 152 + program: Program, 153 + target: Type, 154 + ): boolean | string | number | undefined { 89 155 return program.stateMap(constKey).get(target); 90 156 } 91 157 92 158 /** 159 + * @lexEnum decorator for enum values (boolean, string, or integer arrays) 160 + */ 161 + export function $lexEnum( 162 + context: DecoratorContext, 163 + target: Type, 164 + ...values: Type[] 165 + ) { 166 + const enumValues: (boolean | string | number)[] = []; 167 + for (const value of values) { 168 + const v = value as any; 169 + if (v.kind === "Boolean") { 170 + enumValues.push(v.value); 171 + } else if (v.kind === "String") { 172 + enumValues.push(v.value); 173 + } else if (v.kind === "Number" || v.kind === "Numeric") { 174 + enumValues.push(v.value); 175 + } 176 + } 177 + if (enumValues.length > 0) { 178 + context.program.stateMap(enumKey).set(target, enumValues); 179 + } 180 + } 181 + 182 + export function getLexEnum( 183 + program: Program, 184 + target: Type, 185 + ): (boolean | string | number)[] | undefined { 186 + return program.stateMap(enumKey).get(target); 187 + } 188 + 189 + /** 93 190 * @record decorator for record type lexicons 94 191 */ 95 192 export function $record(context: DecoratorContext, target: Type, key: Type) { ··· 97 194 context.program.stateMap(recordKey).set(target, keyValue); 98 195 } 99 196 100 - export function getRecordKey(program: Program, target: Type): string | undefined { 197 + export function getRecordKey( 198 + program: Program, 199 + target: Type, 200 + ): string | undefined { 101 201 return program.stateMap(recordKey).get(target); 102 202 } 103 203 ··· 184 284 * @errors decorator for declaring error models that an operation might return 185 285 * Error models use their name as the error name and @doc as the optional description 186 286 */ 187 - export function $errors(context: DecoratorContext, target: Type, ...errorModels: Type[]) { 287 + export function $errors( 288 + context: DecoratorContext, 289 + target: Type, 290 + ...errorModels: Type[] 291 + ) { 188 292 const errorList = context.program.stateMap(errorKey).get(target) || []; 189 293 190 294 for (const errorModel of errorModels) { ··· 230 334 context.program.stateMap(errorKey).set(target, errorList); 231 335 } 232 336 233 - export function getErrors(program: Program, target: Type): Array<{ name: string; description?: string }> | undefined { 337 + export function getErrors( 338 + program: Program, 339 + target: Type, 340 + ): Array<{ name: string; description?: string }> | undefined { 234 341 return program.stateMap(errorKey).get(target); 235 342 } 236 343
+28 -15
packages/emitter/src/emitter.ts
··· 33 33 getMaxGraphemes, 34 34 getMinGraphemes, 35 35 getLexConst, 36 + getLexEnum, 36 37 getRecordKey, 37 38 isBlob, 38 39 isRequired, ··· 45 46 isErrorModel, 46 47 getEncoding, 47 48 isInline, 48 - getBytesMaxLength, 49 + getMaxBytes, 50 + getMinBytes, 49 51 } from "./decorators.js"; 50 52 51 53 export interface EmitterOptions { ··· 364 366 ); 365 367 } 366 368 367 - 368 369 private createBlobDef(model: Model): LexiconBlob { 369 370 const blobDef: LexiconBlob = { type: "blob" }; 370 371 ··· 409 410 // Case 1: String enum (string literals with or without string type) 410 411 // isStringEnum: has literals + string type + no refs 411 412 // Closed enum: has literals + no string type + no refs + @closed 412 - if (variants.isStringEnum || 413 - (variants.stringLiterals.length > 0 && 414 - !variants.hasStringType && 415 - variants.unionRefs.length === 0 && 416 - isClosed(this.program, unionType))) { 413 + if ( 414 + variants.isStringEnum || 415 + (variants.stringLiterals.length > 0 && 416 + !variants.hasStringType && 417 + variants.unionRefs.length === 0 && 418 + isClosed(this.program, unionType)) 419 + ) { 417 420 return this.createStringEnumDef(unionType, variants.stringLiterals, prop); 418 421 } 419 422 ··· 440 443 severity: "error", 441 444 message: 442 445 'String literal unions must include "| string" to allow unknown values. ' + 443 - 'Use @closed decorator if this is intentionally a fixed enum.', 446 + "Use @closed decorator if this is intentionally a fixed enum.", 444 447 target: unionType, 445 448 }); 446 449 return null; ··· 756 759 if (param.type?.kind !== "Intrinsic") { 757 760 const inputSchema = this.typeToLexiconDefinition(param.type); 758 761 if (inputSchema) { 759 - def.input = { encoding: encoding || "application/json", schema: inputSchema }; 762 + def.input = { 763 + encoding: encoding || "application/json", 764 + schema: inputSchema, 765 + }; 760 766 } 761 767 } else if (encoding) { 762 768 def.input = { encoding }; ··· 923 929 this.program.reportDiagnostic({ 924 930 code: "array-conversion-failed", 925 931 severity: "error", 926 - message: "Array type conversion failed - array must have a valid item type", 932 + message: 933 + "Array type conversion failed - array must have a valid item type", 927 934 target: model, 928 935 }); 929 936 return null; ··· 1048 1055 if (minGraphemes !== undefined) primitive.minGraphemes = minGraphemes; 1049 1056 } 1050 1057 1051 - private applyBytesConstraints( 1052 - byteDef: any, 1053 - target: Scalar | ModelProperty, 1054 - ) { 1055 - const maxLength = getBytesMaxLength(this.program, target); 1058 + private applyBytesConstraints(byteDef: any, target: Scalar | ModelProperty) { 1059 + const minLength = getMinBytes(this.program, target); 1060 + if (minLength !== undefined) byteDef.minLength = minLength; 1061 + 1062 + const maxLength = getMaxBytes(this.program, target); 1056 1063 if (maxLength !== undefined) byteDef.maxLength = maxLength; 1057 1064 } 1058 1065 ··· 1079 1086 this.isValidConstForType(primitive.type, constValue) 1080 1087 ) { 1081 1088 primitive.const = constValue; 1089 + } 1090 + 1091 + // Apply enum values 1092 + const enumValues = getLexEnum(this.program, prop); 1093 + if (enumValues !== undefined && enumValues.length > 0) { 1094 + primitive.enum = enumValues; 1082 1095 } 1083 1096 1084 1097 // Apply default value
+6 -2
packages/emitter/src/tsp-index.ts
··· 2 2 $maxGraphemes, 3 3 $minGraphemes, 4 4 $lexConst, 5 + $lexEnum, 5 6 $record, 6 7 $blob, 7 8 $required, ··· 13 14 $encoding, 14 15 $errors, 15 16 $inline, 16 - $bytesMaxLength, 17 + $maxBytes, 18 + $minBytes, 17 19 } from "./decorators.js"; 18 20 19 21 /** @internal */ ··· 22 24 maxGraphemes: $maxGraphemes, 23 25 minGraphemes: $minGraphemes, 24 26 lexConst: $lexConst, 27 + lexEnum: $lexEnum, 25 28 record: $record, 26 29 required: $required, 27 30 token: $token, ··· 32 35 encoding: $encoding, 33 36 errors: $errors, 34 37 inline: $inline, 35 - bytesMaxLength: $bytesMaxLength, 38 + maxBytes: $maxBytes, 39 + minBytes: $minBytes, 36 40 }, 37 41 "Tlex.Private": { 38 42 blob: $blob,
+2 -2
packages/emitter/test/integration/atproto/input/com/atproto/sync/subscribeRepos.tsp
··· 45 45 since: tid | null; 46 46 47 47 @doc("CAR file containing relevant blocks, as a diff since the previous repo state. The commit must be included as a block, and the commit block CID must be the first entry in the CAR header 'roots' list.") 48 - @bytesMaxLength(2000000) 48 + @maxBytes(2000000) 49 49 @required 50 50 blocks: bytes; 51 51 ··· 76 76 did: did; 77 77 78 78 @doc("CAR file containing the commit, as a block. The CAR header must include the commit block CID as the first 'root'.") 79 - @bytesMaxLength(10000) 79 + @maxBytes(10000) 80 80 @required 81 81 blocks: bytes; 82 82
+32
packages/emitter/test/spec/basic/input/com/example/arrays.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.arrays { 4 + @doc("Array container types") 5 + model Main { 6 + @doc("Array of strings") 7 + stringArray?: string[]; 8 + 9 + @doc("Array with size constraints") 10 + @minItems(1) 11 + @maxItems(10) 12 + arrayWithLength?: integer[]; 13 + 14 + @doc("Array of object references") 15 + arrayOfRefs?: Item[]; 16 + 17 + @doc("Array of union types") 18 + arrayOfUnion?: (TypeA | TypeB | unknown)[]; 19 + } 20 + 21 + model Item { 22 + value?: string; 23 + } 24 + 25 + model TypeA { 26 + @required a: string; 27 + } 28 + 29 + model TypeB { 30 + @required b: integer; 31 + } 32 + }
+21
packages/emitter/test/spec/basic/input/com/example/blobs.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.blobs { 4 + @doc("Blob type examples") 5 + model Main { 6 + @doc("Basic blob with no constraints") 7 + simpleBlob?: Blob; 8 + 9 + @doc("Image blob up to 5MB") 10 + imageBlob?: Blob<#["image/*"], 5000000>; 11 + 12 + @doc("Specific image types up to 2MB") 13 + specificImages?: Blob<#["image/png", "image/jpeg", "image/gif"], 2000000>; 14 + 15 + @doc("Video blob up to 100MB") 16 + videoBlob?: Blob<#["video/mp4", "video/webm"], 100000000>; 17 + 18 + @doc("Accept any MIME type") 19 + anyBlob?: Blob<#["*/*"]>; 20 + } 21 + }
+20
packages/emitter/test/spec/basic/input/com/example/booleanConstraints.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.booleanConstraints { 4 + @doc("Boolean field constraints") 5 + model Main { 6 + @doc("Boolean with default true") 7 + defaultTrue?: boolean = true; 8 + 9 + @doc("Boolean with default false") 10 + defaultFalse?: boolean = false; 11 + 12 + @doc("Constant true value") 13 + @lexConst(true) 14 + constTrue?: boolean; 15 + 16 + @doc("Constant false value") 17 + @lexConst(false) 18 + constFalse?: boolean; 19 + } 20 + }
+22
packages/emitter/test/spec/basic/input/com/example/bytesConstraints.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.bytesConstraints { 4 + @doc("Bytes field constraints") 5 + model Main { 6 + @doc("Basic bytes field") 7 + simpleBytes?: bytes; 8 + 9 + @doc("Bytes with size constraints") 10 + @minBytes(1) 11 + @maxBytes(1024) 12 + withLength?: bytes; 13 + 14 + @doc("Bytes with minimum size only") 15 + @minBytes(32) 16 + withMinOnly?: bytes; 17 + 18 + @doc("Bytes with maximum size only (5MB)") 19 + @maxBytes(5000000) 20 + withMaxOnly?: bytes; 21 + } 22 + }
+12
packages/emitter/test/spec/basic/input/com/example/cidLinks.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.cidLinks { 4 + @doc("CID link examples") 5 + model Main { 6 + @doc("A CID link to content") 7 + contentCid?: cidLink; 8 + 9 + @doc("Link to previous version") 10 + previousVersion?: cidLink; 11 + } 12 + }
+16
packages/emitter/test/spec/basic/input/com/example/datetimeExamples.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.datetimeExamples { 4 + @doc("Datetime format usage examples") 5 + model Main { 6 + @doc("Required datetime field") 7 + @required 8 + createdAt: datetime; 9 + 10 + @doc("Optional datetime field") 11 + updatedAt?: datetime; 12 + 13 + @doc("Optional publish time") 14 + publishedAt?: datetime; 15 + } 16 + }
+21
packages/emitter/test/spec/basic/input/com/example/identifierExamples.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.identifierExamples { 4 + @doc("Identifier format examples") 5 + model Main { 6 + @doc("DID identifier (did:plc:, did:web:, etc)") 7 + did?: did; 8 + 9 + @doc("Handle identifier (username.bsky.social)") 10 + handle?: handle; 11 + 12 + @doc("Either a DID or handle") 13 + atIdentifier?: atIdentifier; 14 + 15 + @doc("CID as string") 16 + cidString?: cid; 17 + 18 + @doc("BCP 47 language tag") 19 + languageCode?: language; 20 + } 21 + }
+22
packages/emitter/test/spec/basic/input/com/example/integerConstraints.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.integerConstraints { 4 + @doc("Integer field constraints") 5 + model Main { 6 + @doc("Integer between 1 and 100") 7 + @minValue(1) 8 + @maxValue(100) 9 + withMinMax?: integer; 10 + 11 + @doc("Fibonacci numbers only") 12 + @lexEnum(1, 2, 3, 5, 8, 13) 13 + withEnum?: integer; 14 + 15 + @doc("Integer with default value") 16 + withDefault?: integer = 42; 17 + 18 + @doc("Constant integer value") 19 + @lexConst(99) 20 + withConst?: integer; 21 + } 22 + }
+19
packages/emitter/test/spec/basic/input/com/example/nullableFields.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.nullableFields { 4 + @doc("Demonstrates nullable field semantics") 5 + model Main { 6 + @doc("Required, cannot be null or omitted") 7 + @required 8 + requiredField: string; 9 + 10 + @doc("Must be present, but can be null") 11 + nullableRequired?: string | null; 12 + 13 + @doc("Can be omitted, present with value, or present as null") 14 + nullableOptional?: string | null; 15 + 16 + @doc("Can be omitted or present with value, but not null") 17 + optionalField?: string; 18 + } 19 + }
+23
packages/emitter/test/spec/basic/input/com/example/objects.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.objects { 4 + @doc("Object with various property configurations") 5 + model Main { 6 + @doc("This field is required") 7 + @required 8 + requiredField: string; 9 + 10 + @doc("This field is optional") 11 + optionalField?: string; 12 + 13 + @doc("This field can be null") 14 + nullableField?: string | null; 15 + 16 + nestedObject?: Nested; 17 + } 18 + 19 + model Nested { 20 + @required id: string; 21 + label?: string; 22 + } 23 + }
+21
packages/emitter/test/spec/basic/input/com/example/primitives.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.primitives { 4 + @doc("All primitive field types") 5 + model Main { 6 + @doc("A boolean field") 7 + boolField?: boolean; 8 + 9 + @doc("An integer field") 10 + intField?: integer; 11 + 12 + @doc("A string field") 13 + stringField?: string; 14 + 15 + @doc("A bytes field") 16 + bytesField?: bytes; 17 + 18 + @doc("A CID link field") 19 + cidField?: cidLink; 20 + } 21 + }
+44
packages/emitter/test/spec/basic/input/com/example/stringConstraints.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.stringConstraints { 4 + @closed 5 + @inline 6 + union StatusEnum { 7 + "draft", 8 + "published", 9 + "archived", 10 + } 11 + 12 + @doc("String field constraints") 13 + model Main { 14 + @doc("String with byte length constraints") 15 + @minLength(1) 16 + @maxLength(100) 17 + withLength?: string; 18 + 19 + @doc("String with grapheme cluster length constraints") 20 + @minGraphemes(1) 21 + @maxGraphemes(50) 22 + withGraphemes?: string; 23 + 24 + @doc("String with both byte and grapheme constraints") 25 + @minLength(1) 26 + @maxLength(300) 27 + @minGraphemes(1) 28 + @maxGraphemes(100) 29 + withBothLengths?: string; 30 + 31 + @doc("Closed enum of allowed values") 32 + withEnum?: StatusEnum; 33 + 34 + @doc("Open set of suggested values") 35 + withKnownValues?: "en" | "es" | "fr" | "de" | "ja" | string; 36 + 37 + @doc("String with default value") 38 + withDefault?: string = "hello"; 39 + 40 + @doc("Constant string value") 41 + @lexConst("fixed-value") 42 + withConst?: string; 43 + } 44 + }
+39
packages/emitter/test/spec/basic/input/com/example/stringFormats.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.stringFormats { 4 + @doc("All string format types") 5 + model Main { 6 + @doc("Handle or DID") 7 + atIdentifier?: atIdentifier; 8 + 9 + @doc("AT-URI format") 10 + atUri?: atUri; 11 + 12 + @doc("CID in string format") 13 + cid?: cid; 14 + 15 + @doc("ISO 8601 datetime with timezone") 16 + datetime?: datetime; 17 + 18 + @doc("DID identifier") 19 + did?: did; 20 + 21 + @doc("Handle identifier") 22 + handle?: handle; 23 + 24 + @doc("Namespaced identifier") 25 + nsid?: nsid; 26 + 27 + @doc("Timestamp identifier") 28 + tid?: tid; 29 + 30 + @doc("Record key (any syntax)") 31 + recordKey?: recordKey; 32 + 33 + @doc("Generic URI (RFC 3986)") 34 + uri?: uri; 35 + 36 + @doc("IETF BCP 47 language tag") 37 + language?: language; 38 + } 39 + }
+21
packages/emitter/test/spec/basic/input/com/example/uriExamples.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.example.uriExamples { 4 + @doc("Various URI format examples") 5 + model Main { 6 + @doc("AT protocol URI") 7 + atUri?: atUri; 8 + 9 + @doc("Generic URI (https, did, ipfs, etc)") 10 + genericUri?: uri; 11 + 12 + @doc("Record key identifier") 13 + recordKey?: recordKey; 14 + 15 + @doc("Namespaced identifier") 16 + nsid?: nsid; 17 + 18 + @doc("Timestamp identifier") 19 + tid?: tid; 20 + } 21 + }