An experimental TypeSpec syntax for Lexicon

wip

+266 -335
+1 -4
packages/emitter/lib/main.tsp
··· 29 29 * avatar: ImageBlob; 30 30 * ``` 31 31 */ 32 - @Tylex.Private.blob 33 - model Blob<Accept extends valueof unknown = #[], MaxSize extends valueof int32 = 0> { 34 - _blob: never; 35 - } 32 + model Blob<Accept extends valueof unknown = #[], MaxSize extends valueof int32 = 0> {} 36 33 37 34 38 35 /**
-8
packages/emitter/lib/private.decorators.tsp
··· 5 5 * These are not intended for direct use in user code. 6 6 */ 7 7 namespace Tylex.Private; 8 - 9 - /** 10 - * Internal decorator that marks the Blob model. 11 - * This is applied automatically to the Blob model and should not be used directly. 12 - * 13 - * @param target The Blob model 14 - */ 15 - extern dec blob(target: TypeSpec.Reflection.Model);
-13
packages/emitter/src/decorators.ts
··· 11 11 const maxGraphemesKey = Symbol("maxGraphemes"); 12 12 const minGraphemesKey = Symbol("minGraphemes"); 13 13 const recordKey = Symbol("record"); 14 - const blobKey = Symbol("blob"); 15 14 const requiredKey = Symbol("required"); 16 15 const readOnlyKey = Symbol("readOnly"); 17 16 const tokenKey = Symbol("token"); ··· 138 137 target: Type, 139 138 ): string | undefined { 140 139 return program.stateMap(recordKey).get(target); 141 - } 142 - 143 - /** 144 - * @blob private decorator for marking the Blob model 145 - */ 146 - export function $blob(context: DecoratorContext, target: Type) { 147 - // Mark this as a blob model 148 - context.program.stateSet(blobKey).add(target); 149 - } 150 - 151 - export function isBlob(program: Program, target: Type): boolean { 152 - return program.stateSet(blobKey).has(target); 153 140 } 154 141 155 142 /**
+265 -305
packages/emitter/src/emitter.ts
··· 56 56 getMaxGraphemes, 57 57 getMinGraphemes, 58 58 getRecordKey, 59 - isBlob, 60 59 isRequired, 61 60 isReadOnly, 62 61 isToken, ··· 260 259 261 260 private createLexicon(id: string, ns: Namespace): LexiconDoc { 262 261 const description = getDoc(this.program, ns); 263 - const lexicon: LexiconDoc = description 264 - ? { lexicon: 1, id, description, defs: {} } 265 - : { lexicon: 1, id, defs: {} }; 266 - return lexicon; 262 + return { 263 + lexicon: 1, 264 + id, 265 + defs: {}, 266 + ...(description && { description }), 267 + }; 267 268 } 268 269 269 270 private createMainDef(mainModel: Model): LexRecord | LexObject { ··· 377 378 378 379 379 380 private isBlob(model: Model): boolean { 380 - if (isBlob(this.program, model)) return true; 381 + // Check if model itself is named Blob 382 + if (model.name === "Blob") { 383 + return true; 384 + } 381 385 382 - // Check base model 383 - if (model.baseModel && isBlob(this.program, model.baseModel)) return true; 386 + // Check if it's a template instance of Blob 387 + if (isTemplateInstance(model) && model.sourceModel?.name === "Blob") { 388 + return true; 389 + } 384 390 385 - // For template instances, check the source model 386 - if ( 387 - isTemplateInstance(model) && 388 - model.sourceModel && 389 - isBlob(this.program, model.sourceModel) 390 - ) { 391 - return true; 391 + // Check base model (model ImageBlob extends Blob<...>) 392 + if (model.baseModel) { 393 + return this.isBlob(model.baseModel); 392 394 } 393 395 394 396 return false; ··· 397 399 private createBlobDef(model: Model): LexBlob { 398 400 const blobDef: LexBlob = { type: "blob" }; 399 401 400 - // Check both the model itself and the sourceModel for template instances 401 - const templateModel = isTemplateInstance(model) 402 - ? model 403 - : model.sourceModel && isTemplateInstance(model.sourceModel) 404 - ? model.sourceModel 405 - : null; 402 + if (!isTemplateInstance(model)) { 403 + return blobDef; 404 + } 406 405 407 - if (templateModel) { 408 - const templateArgs = templateModel.templateMapper?.args; 409 - if (templateArgs?.length >= 2) { 410 - const acceptArg = templateArgs[0]; 411 - let acceptTypes: string[] | undefined; 406 + const args = model.templateMapper?.args; 407 + if (!args?.length) { 408 + return blobDef; 409 + } 412 410 413 - // Handle ArrayValue 414 - if ( 415 - !isType(acceptArg) && 416 - (acceptArg as ArrayValue).valueKind === "ArrayValue" 417 - ) { 418 - const arrayValue = acceptArg as ArrayValue; 419 - if (arrayValue.values?.length > 0) { 420 - acceptTypes = arrayValue.values 421 - .map((v) => { 422 - if ((v as StringValue).valueKind === "StringValue") { 423 - return (v as StringValue).value; 424 - } 425 - return null; 426 - }) 427 - .filter((v) => v !== null) as string[]; 428 - if (!acceptTypes.length) acceptTypes = undefined; 429 - } 411 + // First arg: accept types (array of mime type strings) 412 + if (args.length >= 1) { 413 + const acceptArg = args[0]; 414 + if (isType(acceptArg) || (acceptArg as ArrayValue).valueKind !== "ArrayValue") { 415 + throw new Error("Blob template first argument must be an array of mime types"); 416 + } 417 + const arrayValue = acceptArg as ArrayValue; 418 + const acceptTypes = arrayValue.values.map(v => { 419 + if ((v as StringValue).valueKind !== "StringValue") { 420 + throw new Error("Blob accept types must be strings"); 430 421 } 431 - 432 - if (acceptTypes) blobDef.accept = acceptTypes; 433 - 434 - const maxSizeArg = templateArgs[1]; 435 - let maxSize: number | undefined; 436 - 437 - // Handle IndeterminateEntity with Number type 438 - const indeterminate = maxSizeArg as IndeterminateEntity; 439 - if ( 440 - indeterminate.entityKind === "Indeterminate" && 441 - indeterminate.type && 442 - isType(indeterminate.type) && 443 - indeterminate.type.kind === "Number" 444 - ) { 445 - maxSize = (indeterminate.type as NumericLiteral).value; 446 - } 422 + return (v as StringValue).value; 423 + }); 424 + if (acceptTypes.length > 0) { 425 + blobDef.accept = acceptTypes; 426 + } 427 + } 447 428 448 - if (maxSize !== undefined && maxSize !== 0) blobDef.maxSize = maxSize; 429 + // Second arg: maxSize (numeric literal) 430 + if (args.length >= 2) { 431 + const maxSizeArg = args[1] as IndeterminateEntity; 432 + if (!isType(maxSizeArg.type) || maxSizeArg.type.kind !== "Number") { 433 + throw new Error("Blob template second argument must be a numeric literal"); 434 + } 435 + const maxSize = (maxSizeArg.type as NumericLiteral).value; 436 + if (maxSize > 0) { 437 + blobDef.maxSize = maxSize; 449 438 } 450 439 } 451 440 452 441 return blobDef; 453 442 } 454 443 455 - private processUnion( 444 + private unionToLexiconProperty( 456 445 unionType: Union, 457 446 prop?: ModelProperty, 458 447 ): LexObjectProperty | null { 459 - // Parse union variants 460 448 const variants = this.parseUnionVariants(unionType); 461 449 462 - // Integer enum (@closed only) 463 - if ( 464 - variants.numericLiterals.length > 0 && 465 - variants.unionRefs.length === 0 && 466 - isClosed(this.program, unionType) 467 - ) { 468 - return this.createIntegerEnumDef( 469 - unionType, 470 - variants.numericLiterals, 471 - prop, 472 - ); 473 - } 474 - 475 450 // Boolean literals are not supported in Lexicon 476 451 if (variants.booleanLiterals.length > 0) { 477 452 this.program.reportDiagnostic({ ··· 484 459 return null; 485 460 } 486 461 462 + // Integer enum (@closed only) 463 + if ( 464 + variants.numericLiterals.length > 0 && 465 + variants.unionRefs.length === 0 && 466 + isClosed(this.program, unionType) 467 + ) { 468 + const propDesc = prop ? getDoc(this.program, prop) : undefined; 469 + const defaultValue = prop?.defaultValue 470 + ? serializeValueAsJson(this.program, prop.defaultValue, prop) 471 + : undefined; 472 + 473 + return { 474 + type: "integer", 475 + enum: variants.numericLiterals, 476 + ...(propDesc && { description: propDesc }), 477 + ...(defaultValue !== undefined && 478 + typeof defaultValue === "number" && { default: defaultValue }), 479 + }; 480 + } 481 + 487 482 // String enum (string literals with or without string type) 488 483 // isStringEnum: has literals + string type + no refs 489 484 // Closed enum: has literals + no string type + no refs + @closed ··· 494 489 variants.unionRefs.length === 0 && 495 490 isClosed(this.program, unionType)) 496 491 ) { 497 - return this.createStringEnumDef(unionType, variants.stringLiterals, prop); 492 + const isClosedUnion = isClosed(this.program, unionType); 493 + const propDesc = prop ? getDoc(this.program, prop) : undefined; 494 + const defaultValue = prop?.defaultValue 495 + ? serializeValueAsJson(this.program, prop.defaultValue, prop) 496 + : undefined; 497 + 498 + const maxLength = getMaxLength(this.program, unionType); 499 + const minLength = getMinLength(this.program, unionType); 500 + const maxGraphemes = getMaxGraphemes(this.program, unionType); 501 + const minGraphemes = getMinGraphemes(this.program, unionType); 502 + 503 + return { 504 + type: "string", 505 + [isClosedUnion ? "enum" : "knownValues"]: variants.stringLiterals, 506 + ...(propDesc && { description: propDesc }), 507 + ...(defaultValue !== undefined && 508 + typeof defaultValue === "string" && { default: defaultValue }), 509 + ...(maxLength !== undefined && { maxLength }), 510 + ...(minLength !== undefined && { minLength }), 511 + ...(maxGraphemes !== undefined && { maxGraphemes }), 512 + ...(minGraphemes !== undefined && { minGraphemes }), 513 + }; 498 514 } 499 515 500 516 // Model reference union (including empty union with unknown) 501 517 if (variants.unionRefs.length > 0 || variants.hasUnknown) { 502 - return this.createUnionRefDef(unionType, variants, prop); 518 + if (variants.stringLiterals.length > 0) { 519 + this.program.reportDiagnostic({ 520 + code: "union-mixed-refs-literals", 521 + severity: "error", 522 + message: 523 + `Union contains both model references and string literals. Atproto unions must be either: ` + 524 + `(1) model references only (type: "union"), or ` + 525 + `(2) string literals + string type (type: "string" with knownValues). ` + 526 + `Separate these into distinct fields or nested unions.`, 527 + target: unionType, 528 + }); 529 + return null; 530 + } 531 + 532 + const isClosedUnion = isClosed(this.program, unionType); 533 + if (isClosedUnion && variants.hasUnknown) { 534 + this.program.reportDiagnostic({ 535 + code: "closed-open-union", 536 + severity: "error", 537 + message: 538 + "@closed decorator cannot be used on open unions (unions containing 'unknown' or 'never'). " + 539 + "Remove the @closed decorator or make the union closed by removing 'unknown'/'never'.", 540 + target: unionType, 541 + }); 542 + } 543 + 544 + const propDesc = prop ? getDoc(this.program, prop) : undefined; 545 + 546 + return { 547 + type: "union", 548 + refs: variants.unionRefs, 549 + ...(propDesc && { description: propDesc }), 550 + ...(isClosedUnion && !variants.hasUnknown && { closed: true }), 551 + }; 503 552 } 504 553 505 554 // Empty union without unknown ··· 592 641 }; 593 642 } 594 643 595 - private createIntegerEnumDef( 596 - unionType: Union, 597 - numericLiterals: number[], 598 - prop?: ModelProperty, 599 - ): LexInteger { 600 - const propDesc = prop ? getDoc(this.program, prop) : undefined; 601 - const defaultValue = prop?.defaultValue 602 - ? serializeValueAsJson(this.program, prop.defaultValue, prop) 603 - : undefined; 604 - 605 - return { 606 - type: "integer", 607 - enum: numericLiterals, 608 - ...(propDesc && { description: propDesc }), 609 - ...(defaultValue !== undefined && 610 - typeof defaultValue === "number" && { default: defaultValue }), 611 - }; 612 - } 613 - 614 - private createStringEnumDef( 615 - unionType: Union, 616 - stringLiterals: string[], 617 - prop?: ModelProperty, 618 - ): LexString { 619 - const isClosedUnion = isClosed(this.program, unionType); 620 - const propDesc = prop ? getDoc(this.program, prop) : undefined; 621 - const defaultValue = prop?.defaultValue 622 - ? serializeValueAsJson(this.program, prop.defaultValue, prop) 623 - : undefined; 624 - 625 - const maxLength = getMaxLength(this.program, unionType); 626 - const minLength = getMinLength(this.program, unionType); 627 - const maxGraphemes = getMaxGraphemes(this.program, unionType); 628 - const minGraphemes = getMinGraphemes(this.program, unionType); 629 - 630 - return { 631 - type: "string", 632 - [isClosedUnion ? "enum" : "knownValues"]: stringLiterals, 633 - ...(propDesc && { description: propDesc }), 634 - ...(defaultValue !== undefined && 635 - typeof defaultValue === "string" && { default: defaultValue }), 636 - ...(maxLength !== undefined && { maxLength }), 637 - ...(minLength !== undefined && { minLength }), 638 - ...(maxGraphemes !== undefined && { maxGraphemes }), 639 - ...(minGraphemes !== undefined && { minGraphemes }), 640 - }; 641 - } 642 - 643 - private createUnionRefDef( 644 - unionType: Union, 645 - variants: ReturnType<typeof this.parseUnionVariants>, 646 - prop?: ModelProperty, 647 - ): LexRefUnion | null { 648 - // Validate: cannot mix refs and string literals 649 - if (variants.stringLiterals.length > 0) { 650 - this.program.reportDiagnostic({ 651 - code: "union-mixed-refs-literals", 652 - severity: "error", 653 - message: 654 - `Union contains both model references and string literals. Atproto unions must be either: ` + 655 - `(1) model references only (type: "union"), or ` + 656 - `(2) string literals + string type (type: "string" with knownValues). ` + 657 - `Separate these into distinct fields or nested unions.`, 658 - target: unionType, 659 - }); 660 - return null; 661 - } 662 - 663 - const isClosedUnion = isClosed(this.program, unionType); 664 - if (isClosedUnion && variants.hasUnknown) { 665 - this.program.reportDiagnostic({ 666 - code: "closed-open-union", 667 - severity: "error", 668 - message: 669 - "@closed decorator cannot be used on open unions (unions containing 'unknown' or 'never'). " + 670 - "Remove the @closed decorator or make the union closed by removing 'unknown'/'never'.", 671 - target: unionType, 672 - }); 673 - } 674 - 675 - const propDesc = prop ? getDoc(this.program, prop) : undefined; 676 - 677 - return { 678 - type: "union", 679 - refs: variants.unionRefs, 680 - ...(propDesc && { description: propDesc }), 681 - ...(isClosedUnion && !variants.hasUnknown && { closed: true }), 682 - }; 683 - } 684 - 685 644 private addOperationToDefs( 686 645 lexicon: LexiconDoc, 687 646 operation: Operation, ··· 773 732 input?: LexXrpcBody; 774 733 parameters?: LexXrpcParameters; 775 734 } { 776 - if (!operation.parameters?.properties?.size) return {}; 735 + if (!operation.parameters?.properties?.size) { 736 + return {}; 737 + } 777 738 778 739 const params = Array.from(operation.parameters.properties) as [ 779 740 string, 780 741 ModelProperty, 781 742 ][]; 782 - const paramCount = params.length; 783 743 784 - // Validate parameter count 785 - if (paramCount > 2) { 744 + if (params.length === 0) { 745 + return {}; 746 + } 747 + 748 + if (params.length > 2) { 786 749 this.program.reportDiagnostic({ 787 750 code: "procedure-too-many-params", 788 751 severity: "error", ··· 793 756 return {}; 794 757 } 795 758 796 - // Handle parameter count cases 797 - if (paramCount === 1) { 798 - return this.handleSingleProcedureParam(params[0], operation); 799 - } else if (paramCount === 2) { 800 - return this.handleTwoProcedureParams(params[0], params[1], operation); 801 - } 802 - 803 - return {}; 804 - } 759 + // Single parameter: must be named "input" 760 + if (params.length === 1) { 761 + const [paramName, param] = params[0]; 762 + if (paramName !== "input") { 763 + this.program.reportDiagnostic({ 764 + code: "procedure-invalid-param-name", 765 + severity: "error", 766 + message: `Procedure parameter must be named "input", got "${paramName}"`, 767 + target: param, 768 + }); 769 + return {}; 770 + } 805 771 806 - private handleSingleProcedureParam( 807 - [paramName, param]: [string, ModelProperty], 808 - operation: Operation, 809 - ): { input?: LexXrpcBody; parameters?: LexXrpcParameters } { 810 - // Validate parameter name 811 - if (paramName !== "input") { 812 - this.program.reportDiagnostic({ 813 - code: "procedure-invalid-param-name", 814 - severity: "error", 815 - message: `Procedure parameter must be named "input", got "${paramName}"`, 816 - target: param, 817 - }); 818 - return {}; 772 + const input = this.buildInput(param); 773 + if (!input) { 774 + return {}; 775 + } 776 + return { input }; 819 777 } 820 778 821 - const input = this.buildInput(param); 822 - return input ? { input } : {}; 823 - } 779 + // Two parameters: must be "input" and "parameters" 780 + const [param1Name, param1] = params[0]; 781 + const [param2Name, param2] = params[1]; 824 782 825 - private handleTwoProcedureParams( 826 - [param1Name, param1]: [string, ModelProperty], 827 - [param2Name, param2]: [string, ModelProperty], 828 - operation: Operation, 829 - ): { input?: LexXrpcBody; parameters?: LexXrpcParameters } { 830 - // Validate first parameter (input) 831 783 if (param1Name !== "input") { 832 784 this.program.reportDiagnostic({ 833 785 code: "procedure-invalid-first-param", ··· 837 789 }); 838 790 } 839 791 840 - // Validate second parameter (parameters) 841 792 if (param2Name !== "parameters") { 842 793 this.program.reportDiagnostic({ 843 794 code: "procedure-invalid-second-param", ··· 847 798 }); 848 799 } 849 800 850 - // Validate that parameters is a plain object 851 801 if (param2.type.kind !== "Model" || (param2.type as Model).name) { 852 802 this.program.reportDiagnostic({ 853 803 code: "procedure-parameters-not-object", ··· 861 811 const input = this.buildInput(param1); 862 812 const parameters = this.buildParametersFromModel(param2.type as Model); 863 813 864 - return { 865 - ...(input && { input }), 866 - ...(parameters && { parameters }), 867 - }; 814 + const result: { input?: LexXrpcBody; parameters?: LexXrpcParameters } = {}; 815 + if (input) { 816 + result.input = input; 817 + } 818 + if (parameters) { 819 + result.parameters = parameters; 820 + } 821 + return result; 868 822 } 869 823 870 824 private buildParametersFromModel( ··· 896 850 897 851 private buildInput(param: ModelProperty): LexXrpcBody | undefined { 898 852 const encoding = getEncoding(this.program, param); 899 - if (param.type?.kind !== "Intrinsic") { 900 - const inputSchema = this.typeToLexiconDefinition(param.type); 901 - if (inputSchema) { 902 - const validSchema = this.toValidBodySchema(inputSchema); 903 - if (validSchema) { 904 - return { 905 - encoding: encoding || "application/json", 906 - schema: validSchema, 907 - }; 908 - } 909 - } 910 - } else if (encoding) { 911 - return { encoding }; 853 + 854 + if (param.type?.kind === "Intrinsic") { 855 + return encoding ? { encoding } : undefined; 856 + } 857 + 858 + const inputSchema = this.typeToLexiconDefinition(param.type); 859 + if (!inputSchema) { 860 + return undefined; 861 + } 862 + 863 + const validSchema = this.toValidBodySchema(inputSchema); 864 + if (!validSchema) { 865 + return undefined; 912 866 } 913 - return undefined; 867 + 868 + return { 869 + encoding: encoding || "application/json", 870 + schema: validSchema, 871 + }; 914 872 } 915 873 916 874 private buildOutput(operation: Operation): LexXrpcBody | undefined { 917 875 const encoding = getEncoding(this.program, operation); 918 - if (operation.returnType?.kind !== "Intrinsic") { 919 - const schema = this.typeToLexiconDefinition(operation.returnType); 920 - if (schema) { 921 - const validSchema = this.toValidBodySchema(schema); 922 - if (validSchema) { 923 - return { encoding: encoding || "application/json", schema: validSchema }; 924 - } 925 - } 926 - } else if (encoding) { 927 - return { encoding }; 876 + 877 + if (operation.returnType?.kind === "Intrinsic") { 878 + return encoding ? { encoding } : undefined; 879 + } 880 + 881 + const schema = this.typeToLexiconDefinition(operation.returnType); 882 + if (!schema) { 883 + return undefined; 884 + } 885 + 886 + const validSchema = this.toValidBodySchema(schema); 887 + if (!validSchema) { 888 + return undefined; 928 889 } 929 - return undefined; 890 + 891 + return { 892 + encoding: encoding || "application/json", 893 + schema: validSchema, 894 + }; 930 895 } 931 896 932 897 private toValidBodySchema( ··· 1142 1107 } 1143 1108 } 1144 1109 1145 - return this.processUnion(unionType, prop); 1110 + return this.unionToLexiconProperty(unionType, prop); 1146 1111 } 1147 1112 1148 1113 private scalarToLexiconPrimitive( ··· 1151 1116 ): LexObjectProperty | null { 1152 1117 // Check if this scalar (or its base) is bytes type 1153 1118 if (this.isScalarBytes(scalar)) { 1154 - let byteDef: LexBytes = { type: "bytes" }; 1155 - byteDef = this.applyBytesConstraints(byteDef, prop || scalar); 1156 - if (prop) byteDef = this.applyPropertyMetadata(byteDef, prop); 1119 + const byteDef: LexBytes = { type: "bytes" }; 1120 + const target = prop || scalar; 1121 + 1122 + const minLength = getMinBytes(this.program, target); 1123 + if (minLength !== undefined) { 1124 + byteDef.minLength = minLength; 1125 + } 1126 + 1127 + const maxLength = getMaxBytes(this.program, target); 1128 + if (maxLength !== undefined) { 1129 + byteDef.maxLength = maxLength; 1130 + } 1131 + 1132 + if (prop) { 1133 + return this.applyPropertyMetadata(byteDef, prop); 1134 + } 1157 1135 return byteDef; 1158 1136 } 1159 1137 1160 1138 // Check if this scalar (or its base) is cidLink type 1161 1139 if (this.isScalarCidLink(scalar)) { 1162 - let cidLinkDef: LexCidLink = { type: "cid-link" }; 1163 - if (prop) cidLinkDef = this.applyPropertyMetadata(cidLinkDef, prop); 1140 + const cidLinkDef: LexCidLink = { type: "cid-link" }; 1141 + if (prop) { 1142 + return this.applyPropertyMetadata(cidLinkDef, prop); 1143 + } 1164 1144 return cidLinkDef; 1165 1145 } 1166 1146 ··· 1173 1153 primitive = { ...primitive, format }; 1174 1154 } 1175 1155 1176 - // Apply constraints 1177 - primitive = this.applyStringConstraints(primitive, prop || scalar); 1178 - primitive = this.applyNumericConstraints(primitive, prop); 1156 + // Apply string constraints 1157 + if (primitive.type === "string") { 1158 + const target = prop || scalar; 1159 + 1160 + const maxLength = getMaxLength(this.program, target); 1161 + if (maxLength !== undefined) { 1162 + primitive.maxLength = maxLength; 1163 + } 1164 + 1165 + const minLength = getMinLength(this.program, target); 1166 + if (minLength !== undefined) { 1167 + primitive.minLength = minLength; 1168 + } 1169 + 1170 + const maxGraphemes = getMaxGraphemes(this.program, target); 1171 + if (maxGraphemes !== undefined) { 1172 + primitive.maxGraphemes = maxGraphemes; 1173 + } 1174 + 1175 + const minGraphemes = getMinGraphemes(this.program, target); 1176 + if (minGraphemes !== undefined) { 1177 + primitive.minGraphemes = minGraphemes; 1178 + } 1179 + } 1180 + 1181 + // Apply numeric constraints 1182 + if (prop && primitive.type === "integer") { 1183 + const minValue = getMinValue(this.program, prop); 1184 + if (minValue !== undefined) { 1185 + primitive.minimum = minValue; 1186 + } 1187 + 1188 + const maxValue = getMaxValue(this.program, prop); 1189 + if (maxValue !== undefined) { 1190 + primitive.maximum = maxValue; 1191 + } 1192 + } 1179 1193 1180 1194 // Apply property-specific metadata 1181 1195 if (prop) { ··· 1186 1200 } 1187 1201 1188 1202 private isScalarBytes(scalar: Scalar): boolean { 1189 - if (scalar.name === "bytes") return true; 1190 - if (scalar.baseScalar) return this.isScalarBytes(scalar.baseScalar); 1191 - return false; 1203 + return scalar.name === "bytes" || (scalar.baseScalar ? this.isScalarBytes(scalar.baseScalar) : false); 1192 1204 } 1193 1205 1194 1206 private isScalarCidLink(scalar: Scalar): boolean { 1195 - if (scalar.name === "cidLink") return true; 1196 - if (scalar.baseScalar) return this.isScalarCidLink(scalar.baseScalar); 1197 - return false; 1207 + return scalar.name === "cidLink" || (scalar.baseScalar ? this.isScalarCidLink(scalar.baseScalar) : false); 1198 1208 } 1199 1209 1200 1210 private getBasePrimitiveType(scalar: Scalar): LexObjectProperty { 1201 1211 if (scalar.name === "boolean") { 1202 1212 return { type: "boolean" }; 1203 - } else if ( 1204 - ["integer", "int32", "int64", "int16", "int8"].includes(scalar.name) 1205 - ) { 1213 + } 1214 + 1215 + if (["integer", "int32", "int64", "int16", "int8"].includes(scalar.name)) { 1206 1216 return { type: "integer" }; 1207 - } else if (["float32", "float64"].includes(scalar.name)) { 1208 - // Lexicon does not support floating-point numbers 1217 + } 1218 + 1219 + if (["float32", "float64"].includes(scalar.name)) { 1209 1220 this.program.reportDiagnostic({ 1210 1221 code: "float-not-supported", 1211 1222 severity: "error", ··· 1214 1225 }); 1215 1226 return { type: "integer" }; 1216 1227 } 1228 + 1217 1229 return { type: "string" }; 1218 - } 1219 - 1220 - private applyStringConstraints( 1221 - primitive: LexObjectProperty, 1222 - target: Scalar | ModelProperty, 1223 - ): LexObjectProperty { 1224 - if (primitive.type !== "string") return primitive; 1225 - 1226 - const result = { ...primitive }; 1227 - const maxLength = getMaxLength(this.program, target); 1228 - if (maxLength !== undefined) result.maxLength = maxLength; 1229 - 1230 - const minLength = getMinLength(this.program, target); 1231 - if (minLength !== undefined) result.minLength = minLength; 1232 - 1233 - const maxGraphemes = getMaxGraphemes(this.program, target); 1234 - if (maxGraphemes !== undefined) result.maxGraphemes = maxGraphemes; 1235 - 1236 - const minGraphemes = getMinGraphemes(this.program, target); 1237 - if (minGraphemes !== undefined) result.minGraphemes = minGraphemes; 1238 - 1239 - return result; 1240 - } 1241 - 1242 - private applyBytesConstraints( 1243 - byteDef: LexBytes, 1244 - target: Scalar | ModelProperty, 1245 - ): LexBytes { 1246 - const result = { ...byteDef }; 1247 - const minLength = getMinBytes(this.program, target); 1248 - if (minLength !== undefined) result.minLength = minLength; 1249 - 1250 - const maxLength = getMaxBytes(this.program, target); 1251 - if (maxLength !== undefined) result.maxLength = maxLength; 1252 - 1253 - return result; 1254 - } 1255 - 1256 - private applyNumericConstraints( 1257 - primitive: LexObjectProperty, 1258 - prop?: ModelProperty, 1259 - ): LexObjectProperty { 1260 - if (!prop || primitive.type !== "integer") return primitive; 1261 - 1262 - const result = { ...primitive }; 1263 - const minValue = getMinValue(this.program, prop); 1264 - if (minValue !== undefined) result.minimum = minValue; 1265 - 1266 - const maxValue = getMaxValue(this.program, prop); 1267 - if (maxValue !== undefined) result.maximum = maxValue; 1268 - 1269 - return result; 1270 1230 } 1271 1231 1272 1232 private applyPropertyMetadata<T extends LexObjectProperty>(
-1
packages/emitter/src/index.ts
··· 21 21 $maxGraphemes, 22 22 $minGraphemes, 23 23 $rec, 24 - $blob, 25 24 $required, 26 25 $readOnly, 27 26 $token,
-4
packages/emitter/src/tsp-index.ts
··· 2 2 $maxGraphemes, 3 3 $minGraphemes, 4 4 $rec, 5 - $blob, 6 5 $required, 7 6 $readOnly, 8 7 $token, ··· 35 34 inline: $inline, 36 35 maxBytes: $maxBytes, 37 36 minBytes: $minBytes, 38 - }, 39 - "Tylex.Private": { 40 - blob: $blob, 41 37 }, 42 38 };