Guideline for Client Emitter
This document provides guidance on using the TypeSpec Client Generator Core (TCGC) in client emitters.
TCGC introduces a client type graph and provides helper functions for generating client code.
Client emitters can rely on the client type graph instead of directly interacting with the TypeSpec core API.
TCGC Library
Section titled “TCGC Library”TCGC abstracts common logic for client emitters across languages, allowing emitters to focus solely on language-specific code generation.
To use TCGC, add it to your package.json:
{ "dependencies": { "@azure-tools/typespec-client-generator-core": "latest" }}In your emitter’s $onEmit function, use createSdkContext to convert EmitContext into SdkContext. The SdkContext.SdkPackage contains the client type graph. See “Client Type Graph” for details.
If your client emitter has options or global variables, extend SdkContext with your custom emitter context. Example:
import { EmitContext } from "@typespec/compiler";import { createSdkContext } from "@azure-tools/typespec-client-generator-core";
interface PythonEmitterOptions extends SdkEmitterOptions { // Options specific to the client emitter}
interface PythonSdkContext extends SdkContext<PythonEmitterOptions> { // Global variables for the client emitter}
export async function $onEmit(context: EmitContext<PythonEmitterOptions>) { const emitterContext: PythonSdkContext = { ...createSdkContext(context), // Initialize global variables };}Exporting TCGC Type Graph
Section titled “Exporting TCGC Type Graph”TCGC can be used as a standalone emitter to export the type graph for debugging. Run:
tsp compile . --emit=@azure-tools/typespec-client-generator-core --options=@azure-tools/typespec-client-generator-core.emitter-name="<emitter-name>"
Replace <emitter-name> with your emitter name to generate the type graph file.
Alternatively, pass the exportTCGCoutput option to createSdkContext to generate the type graph file (<output-dir>/tcgc-output.yaml) alongside client code.
TCGC Playground
Section titled “TCGC Playground”Use the TCGC Playground to experiment with how specifications translate to the TCGC client type graph. Include the playground link when asking questions or reporting issues.
TCGC Flags
Section titled “TCGC Flags”TCGC provides flags to control the client type graph style, such as enabling or disabling convenience APIs. See the documentation for details.
TCGC Raw Types and Helpers
Section titled “TCGC Raw Types and Helpers”In order to introduce the client concept, TCGC introduces some new raw types and helper functions.
SdkClient represents a client and SdkOperationGroup represents a sub client.
Emitters can use listClients to get all the root clients calculated from the current spec. Then emitters can use listOperationGroups to get all the sub clients under one root client or sub client. Finally, emitters can use listOperationsInOperationGroup to get all the operations under one client or sub client.
For these helper functions, the return type is either TCGC raw types or TypeSpec core types.
Client Type Graph
Section titled “Client Type Graph”Unlike TCGC raw types and helpers, the client type graph is a calculated complete type graph that represents your spec. Emitters can use it to get all client info without calling any extra functions. It is recommended to use this type graph instead of calculating all client-related logic with TCGC raw types or TypeSpec core types.
Common Properties
Section titled “Common Properties”Most TCGC types share the following common properties:
namespace: Indicates the type’s namespace.docandsummary: Contain documentation-related information.apiVersions: Indicates which API versions the type exists in.decorators: Stores all TypeSpec decorator info for advanced use cases.crossLanguageDefinitionId: A unique ID for a TCGC type that can be used for output mapping across different emitters.nameandisGeneratedName: The type’s name and whether the name was created by TCGC.access: Indicates whether the type has public or private accessibility.usage: Indicates the type’s usage information; its value is a bitmap ofUsageFlagsenumeration.deprecation: Indicates whether the type is deprecated and provides the deprecation message.clientDefaultValue: The type’s default value if provided. Currently, it only exists in endpoint and API version parameters.
Package
Section titled “Package”SdkPackage represents a client package, containing all clients, operations, and types.
Clients, models, enums, and unions include namespace information. Emitters can use either:
- A flattened structure (
SdkPackage.clients,SdkPackage.enums,SdkPackage.models,SdkPackage.unions) - A hierarchical structure (
SdkPackage.namespaces) requiring iteration through nested namespaces.
License Information
Section titled “License Information”Emitters can get package license info from SdkPackage.licenseInfo. The LicenseInfo contains license details for client code comments or license file generation.
If licenseInfo is undefined, omit license information in the generated code or files.
Use LicenseInfo.name (license name), LicenseInfo.company (company name), LicenseInfo.link (license document link), LicenseInfo.header (header comments), and LicenseInfo.description (license file content) directly when generating license-related content.
For Azure services, emitters should hard-code the license configuration as follows:
export async function $onEmit(context: EmitContext<SdkEmitterOptions>) { context.options.license = { name: "MIT License", company: "Microsoft Corporation", }; const sdkContext = await createSdkContext(context); // ...}Emitters can get first-level clients of a client package from SdkPackage.clients. An SdkClientType represents a client in the package. Emitters can use SdkClientType.children to get nested sub clients, and use SdkClientType.parent to trace back.
SdkClientType.clientInitialization tells emitters how to initialize the client. SdkClientInitializationType contains info about the client’s initialization parameters and how the client can be initialized: by parent client or by itself.
The initialization parameter can be either SdkEndpointParameter, SdkCredentialParameter or SdkMethodParameter.
SdkEndpointParameter is a parameter for the client API endpoint. SdkEndpointParameter.type tells how to compose the endpoint. It can be an SdkEndpointType type or union of SdkEndpointType types if there are multiple ways to compose the endpoint. SdkEndpointType.serverUrl is the base string of the endpoint while SdkEndpointType.templateArguments contains the template arguments used in the SdkEndpointType.serverUrl if they exist.
SdkCredentialParameter is a parameter for how to authorize the client. SdkCredentialParameter.type contains the details of the authorizations. It can be an SdkCredentialType type or union of SdkCredentialType types if there are multiple ways to authorize the client. SdkCredentialType.scheme is the scheme of the authorization method. Currently, TCGC only supports HttpAuth.
SdkMethodParameter is a normal client-level parameter that can be used in some of the methods belonging to the client. For type details, refer to the next section.
Emitters get all methods belonging to a client with SdkClientType.methods. An SdkServiceMethod represents a client’s method.
TCGC supports four kinds of methods: SdkBasicServiceMethod, SdkPagingServiceMethod, SdkLroServiceMethod, and SdkLroPagingServiceMethod.
SdkBasicServiceMethod is a basic method that calls a synchronous server-side API. It contains flags to indicate how the client signature should be generated, and the input and output of the method.
SdkBasicServiceMethod.parameters is the method’s input. Its type SdkMethodParameter contains the type of the parameter along with some attributes of the parameter.
SdkBasicServiceMethod.response is the method’s normal response while SdkBasicServiceMethod.exceptions contains the method’s error responses.
SdkPagingServiceMethod is a paging method that has pageable responses. It extends SdkBasicServiceMethod and contains extra paging information.
SdkLroServiceMethod is an LRO method that calls a long-running server-side API. It extends SdkBasicServiceMethod and contains extra LRO information.
SdkLroPagingServiceMethod is an LRO method that calls a long-running server-side API and has pageable responses. It extends SdkBasicServiceMethod, SdkPagingServiceMethod and SdkLroServiceMethod.
Operation
Section titled “Operation”TCGC separates the client-level operation from the protocol-level operation. This way, TCGC can abstract away the protocol used to call the service (i.e. HTTP or gRPC).
Emitters can get the protocol-level operation from SdkServiceMethod.operation. An SdkServiceOperation represents a protocol operation.
TCGC currently supports one kind of operation: SdkHttpOperation.
SdkHttpOperation contains verb, path, URI template, query/header/path/cookie/body parameters, responses, and exceptions of an HTTP operation.
Each parameter for an HTTP operation has a correspondingMethodParams property to indicate the mapping of one payload parameter with one or more method-level parameters or model properties. This helps emitters determine how to compose the underlying payload with the method’s parameters. One body parameter can have several method-level parameter or model property mappings because of the implicit body parameter resolving from the TypeSpec HTTP library.
For types in TypeSpec, TCGC provides several client types to represent them in a way that’s more similar to client languages.
Built-in Types:
SdkBuiltInTyperepresents a built-in TypeSpec type or ascalartype that derives from a built-in TypeSpec type, excludingutcDateTime,offsetDateTimeandduration. Theencodeproperty is added to these types when the@encodedecorator exists, indicating how to encode when sending to the service.
Date and Time Types:
SdkDateTimeTypeandSdkDurationTypeare converted from TypeSpecutcDateTime,offsetDateTimeanddurationtypes. The datetime encoding info is in theencodeproperty.
Collection Types:
SdkArrayType,SdkTupleTypeandSdkDictionaryTypeare converted from TypeSpecArray,TupleandRecordtypes.
Nullable Types:
SdkNullableTyperepresents a type whose value can be null. The actual type is inSdkNullableType.type.
Enumeration Types:
SdkEnumTypeandSdkEnumValueTyperepresent TCGC enumeration types. They are typically converted from TypeSpecEnumtypes orUniontypes (for extensible enumeration cases).
Literal Types:
SdkConstantTyperepresents a literal type in TypeSpec (StringLiteral,NumericLiteral, orBooleanLiteral).
Union Types:
SdkUnionTyperepresents a TCGC union type. It is typically converted from a TypeSpecUniontype.
Model Types:
SdkModelTyperepresents a TCGC model type. It is typically converted from a TypeSpecModeltype.
Model Property Types:
SdkModelPropertyTyperepresents a TCGC model property type. It is typically converted from a TypeSpecModelPropertytype. It represents a property of a model and has the following key properties:flatten: Indicates if the property can be flattenedadditionalProperties: Indicates if the model can accept additional properties with a specific type- For discriminated models:
discriminatorProperty: The property used as a discriminatordiscriminatedSubtypes: List of all subtypes of this discriminated model
- For subtypes of discriminated models:
discriminatorValue: The instance value for the discriminator for this subtype
Example types
Section titled “Example types”Example types help model the examples that TypeSpec authors define to help users understand how to use the API. TCGC currently only supports examples based on HTTP payload, so the examples are available in SdkHttpOperation.examples.
SdkHttpOperationExample represents an example. SdkHttpParameterExampleValue, SdkHttpResponseExampleValue, and SdkHttpResponseHeaderExampleValue represent HTTP parameter, response body, and response header examples. Each type contains the example value and its corresponding definition type.
SdkExampleValue represents an example value of different types. All related types are used to represent the example value of a definition type. One definition type may have different example value types.
For SdkUnionExampleValue, since it is difficult to determine which union variant the example value should belong to, TCGC preserves the raw value and leaves this determination to the emitter.
For SdkModelExampleValue, TCGC helps map the example type to the correct subtype for discriminated types and separates additional property values from property values. However, for models with inheritance, TCGC does not break down the type graph but instead places all example values in the child model.
Client Type Calculation Logic
Section titled “Client Type Calculation Logic”Client Detection
Section titled “Client Detection”The clients depend on the combination usage of Namespace, Interface, @service, @client, @operationGroup and @moveTo.
If there is no explicitly defined @client or @operationGroup, then the first namespace with @service is a root client. The nested namespaces and interfaces under that namespace are sub clients with hierarchy. Meanwhile, any operations with @moveTo a string type target, is a sub client under the root client.
If there is any @client definition or @operationGroup definition, then each @client is a root client and each @operationGroup is a sub client with hierarchy.
If a detected client or sub client does not contain any sub client or operation, then this client is ignored.
Client Initialization Creation
Section titled “Client Initialization Creation”Normally, a client’s initialization parameters include:
-
Endpoint parameter: Converted from
@serverdefinition on the service the client belongs to.- If the server URL is a constant, TCGC returns a templated endpoint with a default value of the constant server URL.
- When the endpoint has additional template arguments, the type is a union of a completely-overridable endpoint and an endpoint that accepts template arguments.
- If there are multiple servers, TCGC returns the union of all possibilities.
-
Credential parameter: Converted from
@useAuthdefinition on the service the client belongs to. -
API version parameter: If the service is versioned, then the API version parameter on method is elevated to client.
- The API version parameter is detected by parameter name (
api-versionorapiversion) or parameter type (API version enum type used in@versioneddecorator).
- The API version parameter is detected by parameter name (
-
Subscription ID parameter: If the service is an ARM service, then the subscription ID parameter on method is elevated to client.
The client’s initialization way is undefined. Emitters can choose how to initialize all the clients.
With @clientInitialization decorator, the default behavior may change. New client-level parameters are added. Client initialization way can be specified with initializing by parent client, initializing individually or both.
Method Detection
Section titled “Method Detection”The methods depend on the combination usage of Operation, @scope and @moveTo.
A client’s operations include the Operation under the client’s Namespace or Interface, adding any operations with @moveTo current client, deducting any operations with @scope out of current emitter or @moveTo another client.
Method Parameters Handling
Section titled “Method Parameters Handling”If @override is used for the method, the parameters are handled by the target method.
Parameters used in client (either API version parameter or client parameter defined in @clientInitialization) are filtered from method parameter list.
Method Return Type Calculation
Section titled “Method Return Type Calculation”The method’s return type is determined by the underlying operation’s normal responses:
- If
@responseAsBoolis on the method, then the response is a boolean. - If the responses contain multiple return types, the return type is a union of all the types.
- If the responses contain empty return type, the return type is wrapped with a nullable type.
The paging method’s return type is an array type of the page items type. It is inferred from @pageItems or @items decorator.
The LRO method’s return type is the final response type. It is inferred from LroMetadata.
HTTP Operation Parameters Handling
Section titled “HTTP Operation Parameters Handling”The HTTP operation’s parameters are inferred from TypeSpec HTTP lib type HttpOperationParameters. Different HTTP parameters are handled by specific logic based on the info from TypeSpec HTTP lib type HttpOperationParameter.
TCGC infers the body parameter type from TypeSpec HTTP lib type HttpOperationBody. If the body is explicitly defined (with @body or @bodyRoot), TCGC uses the type directly as the body type. If not, TCGC treats the body parameter as a spread case. For such body types, TCGC tries to get back the original model if all the spread properties are from one model. Otherwise, TCGC creates a new model type for the body parameter.
TCGC creates the Content-Type header parameter for any operation with body parameter if it doesn’t exist, and creates the Accept header parameter for any operation with response that contains body. TCGC also creates corresponding method parameters for the operation’s upper layer method for each case.
TCGC uses several ways to find an HTTP operation’s parameter’s corresponding method parameter or model property:
- Check if the parameter is a client-level method parameter.
- Check if the parameter is an API version parameter that has been elevated to client.
- Check if the parameter is a subscription parameter that has been elevated to client (only for ARM services).
- Check if the parameter is a method parameter or a nested model property of a method parameter (nested HTTP metadata case when using
@bodyRoot). - Check if all properties of the parameter can be mapped to a method parameter or a nested model property of a method parameter (spread).
HTTP Operation Response Calculation
Section titled “HTTP Operation Response Calculation”The response is inferred from TypeSpec HTTP lib type HttpOperationResponse.
For each response, TCGC will check the response’s content. If contents from different responses are not equal, TCGC takes the last one as the response type. Any response with * status code or response content type that has @error decorator, TCGC puts them into the exception response list. Others are put in the response list.
If @responseAsBool is on the operation’s upper level method, the 404 status code is always recognized as a normal response.
Type Detection
Section titled “Type Detection”TCGC uses the following steps to detect all the types in one spec:
- Starts from the root clients and iterates all nested clients to find the methods.
- For all methods, iterates all method’s parameters to find types.
- For all methods’ underlying operations, iterates all operation’s parameters, body parameter, response body, response header to find types.
- If type is a
Union, iterates all union variants to find types. - If type is a
Model, iterates all model properties to find types, finds types for additional properties if they exist and finds types for model’s base model. - If type is a
Modelwith@discriminator, iterates all sub-types belonging to it. - If type is an
ArrayorRecord, finds types for the value type. - If type is a
Tuple, iterates all values to find types. - If type is an
EnumMember, finds types for the enum the member belongs to. - If type is a
UnionVariant, finds types for the union the variant belongs to. - Iterates parameters defined in
@serverand finds types. - Iterates user-defined namespace for
Model,EnumandUnionto find orphan types (not referred byOperation). - Handles API version
Enumused in@versioned.
Access Calculation
Section titled “Access Calculation”If there is no @access used in the spec, all model properties, types, and methods in TCGC have public accessibility.
If @access is decorated on either Namespace, Operation, types, or model properties, the accessibility is overridden with the new logic.
Usage Calculation
Section titled “Usage Calculation”If there is no @usage used in the spec, all types’ usage in TCGC is calculated by the place where the type is used. The @usage decorator can extend the usage for one type or all types under one namespace. The calculation logic is here.
Naming Logic for Anonymous Types
Section titled “Naming Logic for Anonymous Types”SdkModelType, SdkEnumType, SdkUnionType, and SdkConstantType always have names. If the original TypeSpec type does not have a name, TCGC creates a name for it. The naming logic follows these steps:
-
Find the place where the type is used:
- Find if the type is used in the method’s underlying operation: either in parameters, body parameter, response header, or response body.
- Find if the type is used in the method’s parameters.
- Find if the type is used in orphan types.
-
With the path of where the type is used:
- Reversely look up the first path segment whose name is not empty and the segment is a model, union, or method.
- Create the name with the model, union, or method’s name, concatenating with all segments’ names starting after that segment.
- If a segment is an HTTP parameter, the concatenated name is
Request+ parameter name in PascalCase. - If a segment is an HTTP body parameter, the concatenated name is
Request. - If a segment is an HTTP response header, the concatenated name is
Response+ header name in PascalCase. - If a segment is an HTTP response body, the concatenated name is
Response. - If a segment is a method’s parameter, the concatenated name is
Parameter+ parameter name in PascalCase. - If a segment is a model’s additional property, the concatenated name is
AdditionalProperty. - If a segment is a model’s property, the concatenated name is the property name in PascalCase, and the property name is converted to singular if the property type is an array or dictionary.
Extending the decorator allowlist (additionalDecorators)
Section titled “Extending the decorator allowlist (additionalDecorators)”By default, TCGC only includes decorators that are on a safe allowlist when populating the decorators arrays on the client type graph. Language emitters that need to include extra decorators (for example to enable core/custom decorators in the generated SDK model) can append regular-expression strings to that allowlist using the additionalDecorators option passed to createSdkContext.
Each entry should be a string containing a regular expression matched against the fully-qualified decorator name (for example: Azure.ClientGenerator.Core.@override). Because these are provided as JavaScript strings, backslashes must be escaped (see example).
Example (inside an emitter or emitter class):
this.sdkContext = await createSdkContext(this.emitterContext, LIB_NAME, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@override"], versioning: { previewStringRegex: /$/ },}); // include all versions and do the filter by ourselvesThese patterns are appended to the default allowlist used by TCGC; matched decorators will be included on the resulting Sdk* types’ decorators lists.