typespec Documentation

Java json-schema & openapi3 repository by @microsoft. OpenAPI 3.x specifications & schema with 5.9k stars.

5,896 stars Java Java 128 files · ~127,158 tokens #json-schema#openapi3#protobuf#typespec
RAW Doc

Technical Documentation: microsoft/typespec

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

TypeSpec

Official Docs | Try TypeSpec Online | Getting Started | Language Overview

TypeSpec is a language for defining cloud service APIs and shapes. TypeSpec is a highly extensible language with primitives that can describe API shapes common among REST, OpenAPI, gRPC, and other protocols.

TypeSpec is excellent for generating many different API description formats, client and service code, documentation, and other assets while keeping your TypeSpec definition as a single source of truth.

Using TypeSpec, you can create reusable patterns for all aspects of an API and package those reusable patterns into libraries. These patterns establish "guardrails" for API designers and make it easier to follow best practices than to deviate from them. TypeSpec also has a rich linter framework with the ability to flag anti-patterns as well as an emitter framework that lets you control the output to ensure it follows the patterns you want.

TypeSpec is a Microsoft-built, community-supported project. Your ideas, feedbacks, and code make all the difference and we deeply appreciate the support from the community.

[Installation](https://typespec.io/docs)

text
npm install @typespec/compiler

Tools

The TypeSpec VS Code extension can be installed from the VS Code marketplace or directly on the command line:

text
tsp code install

The TypeSpec VS Extension can be installed from the VS Marketplace or directly on the command line:

text
tsp vs install

[Usage](https://typespec.io/docs#create-first-typespec-project)

TypeSpec to OpenAPI 3.0 Example

This example uses the @typespec/http, @typespec/rest, and @typespec/openapi3 libraries to define a basic REST service and generate an OpenAPI 3.0 document from it.

Run the following command and select "Generic REST API":

text
tsp init

Hit enter a few times to confirm the defaults.

Copy the contents below into your main.tsp:

typespec
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi3";

using Http;
using Rest;

/** This is a pet store service. */
@service(#{ title: "Pet Store Service" })
@server("https://example.com", "The service endpoint")
namespace PetStore;

@route("/pets")
interface Pets {
  list(): Pet[];
}

model Pet {
  @minLength(100)
  name: string;

  @minValue(0)
  @maxValue(100)
  age: int32;

  kind: "dog" | "cat" | "fish";
}

Install the dependencies of main.tsp:

text
tsp install

Compile it to OpenAPI 3.0:

text
tsp compile main.tsp --emit @typespec/openapi3

You can find the emitted OpenAPI output in ./tsp-output/openapi.json.

Advanced Scenarios

Installing nightly version

On every commit to the main branch, packages with changes are automatically published to npm with the @next tag.
The packages section shows which version corresponds to the next tag for each package.

To use a nightly version of the packages, go over each one of the packages in the package.json file and update it to either the latest published @next version or @latest, whichever is the newest. You can also use the tag latest or next instead of an explicit version.

After updating the package.json file you can run npm update --force. Force is required as there might be some incompatible version requirement.

Example

json5
// Stable setup
"dependencies": {
  "@typespec/compiler": "~0.30.0",
  "@typespec/http": "~0.14.0",
  "@typespec/rest": "~0.14.0",
  "@typespec/openapi": "~0.9.0",
}

// Consume next version
// In this example: compiler and openapi have changes but rest library has none
"dependencies": {
  "@typespec/compiler": "~0.31.0-dev.5",
  "@typespec/http": "~0.14.0",
  "@typespec/rest": "~0.14.0", // No changes to @typespec/rest library so need to stay the latest.
  "@typespec/openapi": "~0.10.0-dev.2",
}

Packages

Name Changelog Latest Next
Core functionality
@typespec/compiler Changelog
TypeSpec Libraries
@typespec/http Changelog
@typespec/rest Changelog
@typespec/openapi Changelog
@typespec/openapi3 Changelog
@typespec/versioning Changelog
TypeSpec Tools
@typespec/prettier-plugin-typespec Changelog
typespec-vs Changelog
typespec-vscode Changelog
tmlanguage-generator Changelog

@next version of the package are the latest versions available on the main branch.


Overview (`docker/readme.md`)

Use TypeSpec via Docker

Image: azsdkengsys.azurecr.io/typespec

Tags:

  • Lastest from master: alpine, latest

Usage

bash
docker run \
  -v "${pwd}:/wd" --workdir="/wd" \
  -t azsdkengsys.azurecr.io/typespec
# ... TypeSpec args ...

For usage in PowerShell replace \ with `

Install dependencies

bash
docker run -v "${pwd}:/wd" --workdir="/wd" -t azsdkengsys.azurecr.io/typespec install

Compile

bash
docker run -v "${pwd}:/wd" --workdir="/wd" -t azsdkengsys.azurecr.io/typespec compile .

Init a new project

bash
docker run -v "${pwd}:/wd" --workdir="/wd" -t azsdkengsys.azurecr.io/typespec init

Overview (`docs/readme.md`)

Docs moved

Docs have moved into the website folder ../website/src/content/docs/docs

For reference

This is an unfortunate current limitation of astro starlight. If things change in the future this is the current limitations:

  • content folder MUST be under the astro website package(default src/content)
  • astro allows symlink but it MUST be the whole collection(e.g. src/content/docs)
  • starlight only way to put docs under a docs/ path is to have another docs/ folder under the collection one src/content/docs (resulting in src/content/docs/docs)

If any of the issues above is improve we could potentially move the docs back here or reduce at least some nesting


Overview (`website/README.md`)

Starlight Starter Kit: Basics

text
npm create astro@latest -- --template starlight




🧑‍🚀 Seasoned astronaut? Delete this file. Have fun!

🚀 Project Structure

Inside of your Astro + Starlight project, you'll see the following folders and files:

text
.
├── public/
├── src/
│   ├── assets/
│   ├── content/
│   │   ├── docs/
│   │   └── config.ts
│   └── env.d.ts
├── astro.config.mjs
├── package.json
└── tsconfig.json

Starlight looks for .md or .mdx files in the src/content/docs/ directory. Each file is exposed as a route based on its file name.

Images can be added to src/assets/ and embedded in Markdown with a relative link.

Static assets, like favicons, can be placed in the public/ directory.

🧞 Commands

All commands are run from the root of the project, from a terminal:

Command Action
npm install Installs dependencies
npm run dev Starts local dev server at localhost:4321
npm run build Build your production site to ./dist/
npm run preview Preview your build locally, before deploying
npm run astro ... Run CLI commands like astro add, astro check
npm run astro -- --help Get help using the Astro CLI

👀 Want to learn more?

Check out Starlight’s docs, read the Astro documentation, or jump into the Astro Discord server.


Overview (`.github/workflows/README.md`)

TypeSpec Workflows

This directory contains the GitHub workflows used for TypeSpec repository CI/CD processes.

Available Workflows

Workflow Description
consistency.yml Ensures code consistency, including changelog, spellcheck, formatting, and linting
codeql.yml Runs CodeQL analysis for code security
external-integration.yml Optional CI check that verifies compatibility with Azure/typespec-azure repository

TypeSpec-Azure Integration Check

The external-integration.yml workflow verifies that changes in the TypeSpec repository do not break compatibility with the Azure/typespec-azure repository, which depends on TypeSpec as a core dependency.

How It Works

  1. Clones the Azure/typespec-azure repository
  2. Finds the "core" submodule that references microsoft/typespec
  3. Updates that submodule to use the changes from the current PR
  4. Runs the build and tests for the Azure/typespec-azure repository

When It Runs

This check runs on:

  • Pull requests to the main branch
  • Manual triggers via workflow_dispatch

What It Checks

  • Whether the Azure/typespec-azure repository can build successfully with the changes from the PR
  • Whether all tests in the Azure/typespec-azure repository pass with the changes

Configuration

This is an optional check that won't block PRs from being merged. It only runs on Linux with Node LTS (24.x) to minimize resource usage while still catching compatibility issues.


Overview (`eng/emitter-diff/README.md`)

emitter-diff

A language-agnostic tool for diffing the generated code produced by two versions of a
TypeSpec emitter
.

It runs the emitter's own regenerate command against a baseline source tree and a head
source tree, then shows the diff between the two generated outputs. Use it locally during
development and in CI on PRs.

The tool contains zero language logic. An emitter integrates by naming three things — its
regenerate command, the package directory to run it in, and the generated-code directory to diff —
either as flags or via a built-in --emitter preset.

How it works

text
baseline tree ─► run <command> in <emitter-path> ─► snapshot <generated-code-path> ─┐
                                                                                        ├─► git diff ─► terminal / HTML
   head tree ─────► run <command> in <emitter-path> ─► snapshot <generated-code-path> ─┘
  • A source tree is resolved for each side (--baseline / --head).
  • The --command is run verbatim (tokenized to argv — no shell) inside
    /. This is the emitter's unmodified regenerate command; the tool does
    not reach into it.
  • The /<generated-code-path> subtree is snapshotted for each side and the
    two snapshots are diffed. --generated-code-path accepts multiple roots — an emitter that
    writes generated code to several directories (e.g. Go) passes a comma-separated list or repeats
    the flag; each root is snapshotted under its own relative path so outputs never collide.

Because the tool just runs a command, any emitter with a regenerate script works — no per-language
plugin code.

Usage

bash
# Using a built-in preset (fills in command + paths):
node eng/emitter-diff/src/cli.ts --emitter python --baseline gh:<sha>

# Fully explicit (no preset needed):
node eng/emitter-diff/src/cli.ts \
  --command "npm run regenerate" \
  --emitter-path packages/http-client-python \
  --generated-code-path tests/generated \
  --baseline gh:<sha>

# Multiple generated roots (e.g. Go writes several) — comma-separated (or repeat the flag):
node eng/emitter-diff/src/cli.ts \
  --command "npm run tspcompile" \
  --emitter-path packages/typespec-go \
  --generated-code-path test/http-specs,test/azure-http-specs \
  --baseline github:Azure/autorest.go@<sha>

This tool is a set of plain .ts scripts — not an installed package. It runs through node
(which executes TypeScript directly on the versions this repo supports), so there is nothing to
build. Typecheck with npx tsc -p eng/emitter-diff.

Build your checkout first. --head defaults to the current working tree, and the tool runs
the regenerate command against it as-is — it never installs or builds your checkout (see
Command prep). Build the emitter for the head side before diffing; for
python that's npm run setup in packages/http-client-python (builds the emitter and creates
the venv regenerate requires). Only trees the tool fetches from GitHub are auto-prepared.

Emitter config

Flag Meaning
--emitter <name> Built-in preset that fills in the three fields below.
--command <cmd> Regenerate command, run verbatim in --emitter-path.
--emitter-path Package dir (relative to a tree root) to run the command in.
--generated-code-path Generated-code dir (relative to --emitter-path) to diff. Accepts multiple roots — comma-separated (a,b) or by repeating the flag — for an emitter with several generated roots (e.g. Go); each is snapshotted under its own relative path.

A preset supplies all three; each flag still overrides the preset value. To onboard a new language,
add a row to EMITTER_DEFAULTS in src/registry.ts — or just pass the three flags directly and
skip --emitter.

Refs

Syntax Meaning
local:/path or ./path a local source folder (run in place)
github:owner/repo@<sha|branch> a GitHub source at a ref
gh:<sha|branch> this repo (origin remote) at a ref

--head defaults to the current working tree. --baseline defaults to the upstream remote's
repo at its default branch, falling back to origin (e.g. github:microsoft/typespec@main).

Command prep (`--setup`)

The --command is run as-is — it does not install deps or build. Instead, prep is handled by
--setup, which runs only in a tree the tool freshly fetched from GitHub (a gh:/github:
ref). The current working tree and user-provided local: paths are assumed already built and are
never touched (so setup never mutates your checkout or a prepared CI worktree).

  • A preset supplies sensible setup defaults, so --emitter python --baseline gh:main installs +
    builds the fetched baseline automatically (python: npm install --ignore-scripts → npm run setup;
    typescript: pnpm install → npm run build).
  • Override with one or more --setup <cmd> (each runs in order, in /), or
    disable entirely with --no-setup.

Each --setup command — like --command — is tokenized to argv and run without a shell, so
multi-step pipelines (&&, |) are not supported; pass repeated --setup flags instead.

Common options

By default the tool writes a clickable HTML report (emitter-diff.html) into the work dir and
prints a file:// link to it.

  • --baseline <ref> / --head <ref>: the two source trees to compare.
  • --setup <cmd> (repeatable) / --no-setup: prep commands for freshly fetched GitHub trees.
  • --work-dir <dir>: scratch dir for snapshots (default: a fresh temp dir).
  • --sequential: regenerate baseline then head one after another instead of in parallel. Useful on
    a single machine where running both at once oversubscribes the CPU (each regenerate already fans
    out across cores) or trips generator races.
  • --ci: disable the local baseline-output cache (intended for CI).
  • --html <file>: write the rendered HTML report to this path.
  • --md <file>: write a Markdown report (collapsible per-file diff blocks) to this path. Handy for
    a CI job summary ($GITHUB_STEP_SUMMARY) or a PR comment body.
  • --no-group: render every file separately in the HTML/Markdown reports. By default, files that
    share the same change — the same added/removed lines, even when the surrounding generated code
    differs — are merged into a single collapsible group (with the shared diff shown once and every
    once and every affected file listed), so a repeated change across many generated files is reviewed
    once instead of N times.
  • --fail-on-diff: exit non-zero when output differs (exit 2 = diff present, 1 = hard error).
  • -- <args>: everything after -- is appended to --command verbatim on both sides, so the
    diff stays apples-to-apples. Use it to regenerate only a subset of tests by forwarding the
    regenerate script's own filter flags.

Regenerating a subset of tests

The tool doesn't define its own test-filter flags — it forwards -- <args> to each emitter's
regenerate command, which owns the filtering. For the Python emitter (regenerate.ts):

  • -n, --name : case-insensitive substring match on package name.
  • -f, --flavor <azure|unbranded>: limit to one flavor.
  • -j, --jobs <n>: parallel job count.
sh
# Only the authentication packages, azure flavor
node ../../eng/emitter-diff/src/cli.ts --emitter python -- --name authentication --flavor azure

# Via the package script (first `--` is npm's, second is emitter-diff's passthrough separator)
npm run diff-spector-tests -- -- --name type/array

--name filters the spec set already bundled in the package's node_modules
(@azure-tools/azure-http-specs + @typespec/http-specs); it does not point the test set at an
arbitrary folder or ref. Other emitters expose their own filter flags — pass whatever their
regenerate command accepts.

CI integration

.github/workflows/ci-emitter-diff-<lang>.yml runs on PRs that touch the language emitter or this
tool. The baseline is the base-branch commit the PR is based on (the git merge-base with the
target branch). Because the tool runs the regenerate command as-is, the workflow prepares both
trees
(installs deps, builds the emitter, creates any venv) before invoking the tool, then:

  • writes a job summary with the rendered diff,
  • relays a sticky PR comment for fork and same-repository PRs, and
  • uploads the rendered HTML report as an artifact.

Informational: the check always passes unless the tool hits a real tool/build error — a
generated-output diff does not fail the PR. CI runs the tool without --fail-on-diff, so a diff
still exits 0; only a non-zero exit (a build/venv/generate failure) fails the job.

Fork PR comments use an artifact relay. The Python producer stays on pull_request with only
contents: read and checkout credentials not persisted. It uploads a small
emitter-diff-python-comment JSON artifact (retained for one day), including a failure message
if setup or generation fails. Fork runs may require maintainer approval under the repository's
Actions policy.

.github/workflows/ci-emitter-diff-python-commenter.yml listens for completed python / emitter diff
runs using workflow_run, with only actions: read and pull-requests: write. It never checks out,
builds, or executes PR code or downloaded scripts. The artifact supplies only bounded comment text;
the listener resolves exactly one open PR using the trusted run's head repository, branch, and SHA.
It ignores artifact PR numbers and markers, skips stale/closed/ambiguous PRs, and rechecks the current
head before posting. It paginates comments and updates only its fixed-marker, GitHub Actions bot-owned
comment. Per-branch serialization and a trusted run stamp prevent an older run overwriting a newer
comment.

Cancelled or skipped runs do not post comments. A failed run without a comment artifact produces
an explicit warning; missing artifacts from successful runs, malformed payloads, and API/download
errors fail the listener rather than silently reporting success.

To review a fork PR's diff, open its python / emitter diff check and follow Details to the
workflow run. Read the rendered diff in the job summary or download emitter-diff-html from
the run's Artifacts section (retained for seven days). Manual workflow_dispatch runs also
produce reports without posting PR comments.

The listener must land on the repository's default branch before GitHub will trigger it.
An existing PR also needs a new producer run using the updated workflow revision; rerunning an old
run retains its original revision. After these workflow changes land, updating the PR branch from
its target branch and pushing starts a new run with the comment artifact. Local listener tests do
not establish that hosted fork commenting works before that default-branch activation.

Adding a new language

Either add a preset row to EMITTER_DEFAULTS (src/registry.ts):

ts
rust: {
  command: "npm run tspcompile",
  emitterPath: "packages/typespec-rust",
  generatedCodePath: "test/generated",
},

…or skip the preset entirely and pass --command / --emitter-path /
--generated-code-path directly. Either way, ensure each side's tree can actually run the
command (see Command prep above). The orchestrator, ref resolver, and diff engine need no
changes.

Notes & limitations

  • --html renders a self-contained, GitHub-style HTML report (inline CSS, no external requests).
    --md renders a Markdown report (collapsible per-file diff blocks) suitable for a CI job
    summary or PR comment. Both reports group files that share the same change into one block by
    default (--no-group to disable), so reviewing a repeated diff is a one-time effort. The diff
    itself is produced by git diff --no-index; the tool leans on only a couple of small repo dev
    dependencies (execa, picocolors) for process spawning and terminal coloring.
  • For github refs (including fork repos), the resolver uses a detached, commit-keyed cached git
    worktree under the temp directory, created from an isolated cache repo (not from your active
    checkout). Repeated runs on the same commit reuse it.
  • Spec inputs come from each side's own dependencies (e.g. node_modules/@typespec/http-specs).
    Spec versions rarely change, so any drift between baseline/head is treated as acceptable noise.

Overview (`icons/raw/readme.md`)

TypeSpec Raw Svg Icons

Those are the raw TypeSpec icons with text still has text. It can be used to easily convert the text to path with different font and different positions.


Overview (`packages/astro-utils/README.md`)

TypeSpec Astro Utils

This is a bunch of tools for various components of astro(shiki, starlight, expressive code) that is shared across typespec.io and azure.github.io/typpespec-azure website.

DO NOT PUBLISH


Overview (`packages/best-practices/README.md`)

TypeSpec Best Practices

WORK IN PROGRESS This is not ready for consumption

Set of linter rules to enforce TypeSpec best practices


Overview (`packages/compiler/README.md`)

TypeSpec compiler and CLI

This package implements the core of the TypeSpec
compiler and its command-line interface.

See also


Overview (`packages/events/README.md`)

@typespec/events

TypeSpec library providing events bindings

Install

bash
npm install @typespec/events

Decorators

TypeSpec.Events

`@contentType`

Specifies the content type of the event envelope, event body, or event payload.
When applied to an event payload, that field must also have a corresponding @data
decorator.

typespec
@TypeSpec.Events.contentType(contentType: valueof string)

Target

UnionVariant | ModelProperty

Parameters

Name Type Description
contentType valueof string The content type of the event or event payload.

Examples

typespec
@events
union MixedEvents {
  @contentType("application/json")
  message: {
    id: string,
    text: string,
  },
}

Specify the content type of the event payload.

typespec
@events
union MixedEvents {
  {
    done: true,
  },
  {
    done: false,
    @data @contentType("text/plain") value: string,
  },
}

`@data`

Identifies the payload of an event.
Only one field in an event can be marked as the payload.

typespec
@TypeSpec.Events.data

Target

ModelProperty

Parameters

None

Examples

typespec
@events
union MixedEvents {
  {
    metadata: Record<string>,
    @data payload: string,
  },
}

`@events`

Specify that this union describes a set of events.

typespec
@TypeSpec.Events.events

Target

Union

Parameters

None

Examples

typespec
@events
union MixedEvents {
  pingEvent: string,
  doneEvent: "done",
}

Overview (`packages/graphql/README.md`)

@typespec/graphql

TypeSpec library for emitting GraphQL

Install

bash
npm install @typespec/graphql

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/graphql
  1. Via the config
yaml
emit:
  - "@typespec/graphql"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/graphql"
options:
  "@typespec/graphql":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/graphql
See Configuring output directory for more info

`output-file`

Type: string

Name of the output file.
Output file will interpolate the following values:

  • schema-name: Name of the schema if multiple

Default: {schema-name}.graphql

Example Single schema

  • schema.graphql

Example Multiple schemas

  • Org1.Schema1.graphql
  • Org1.Schema2.graphql

`new-line`

Type: "crlf" | "lf"

Default: "lf"

Set the newLine character for emitting files.

`omit-unreachable-types`

Type: boolean

Omit unreachable types.
By default all types declared under the schema namespace will be included.
With this flag on only types references in an operation will be emitted.

Decorators

TypeSpec.GraphQL

`@compose`

Specify the GraphQL interfaces that should be implemented by a model.
The interfaces must be decorated with the @graphqlInterface decorator,
and all of the interfaces' properties must be present and compatible.

typespec
@TypeSpec.GraphQL.compose(...interfaces: Model[])

Target

Model

Parameters

Name Type Description
interfaces Model[] The models to compose into the target, each emitted as a GraphQL interface.

Examples

typespec
@graphqlInterface(#{ interfaceOnly: true })
model Node {
  id: string;
}

@compose(Node)
model User {
  ...Node;
  name: string;
}

`@graphqlInterface`

Mark this model as a GraphQL Interface. Interfaces can be implemented by other models
using the @compose decorator.

typespec
@TypeSpec.GraphQL.graphqlInterface(options?: valueof { interfaceOnly: boolean })

Target

Model

Parameters

Name Type Description
options valueof {...} .interfaceOnly When true, the model will only be emitted as an interface(no "Interface" suffix is added to the name). Use this for abstract interfaces thatwill never be used directly as output/input types (e.g., Node, Connection). Defaults to false.

Examples

typespec
@graphqlInterface(#{ interfaceOnly: true })
model Node {
  id: string;
}

@compose(Node)
model User {
  ...Node;
  name: string;
}
// Emits: interface Node { id: String! }
//        type User implements Node { id: String!; name: String! }

`@mutation`

Specify the GraphQL Operation kind for the target operation to be MUTATION.

typespec
@TypeSpec.GraphQL.mutation

Target

Operation

Parameters

None

Examples

typespec
@mutation op createUser(name: string): User;

`@operationFields`

Assign one or more operations or interfaces to act as fields with arguments on a model.
The operations become fields on the GraphQL type with their parameters as arguments.

typespec
@TypeSpec.GraphQL.operationFields(...operations: Operation | Interface[])

Target

Model

Parameters

Name Type Description
operations Operation | Interface[] The operations, or interfaces of operations, to add to the target as fields.

Examples

typespec
op followers(query: string): Person[];

@operationFields(followers)
model Person {
  name: string;
}
// Emits: type Person { name: String!; followers(query: String!): [Person!]! }

`@query`

Specify the GraphQL Operation kind for the target operation to be QUERY.

typespec
@TypeSpec.GraphQL.query

Target

Operation

Parameters

None

Examples

typespec
@query op getUser(id: string): User;

`@schema`

Mark this namespace as describing a GraphQL schema and configure schema properties.
All types and operations within the namespace will be emitted to a single GraphQL schema file.

typespec
@TypeSpec.GraphQL.schema(options?: valueof TypeSpec.GraphQL.Schema.SchemaOptions)

Target

Namespace

Parameters

Name Type Description
options valueof SchemaOptions Options for the schema, such as its name.

Examples

typespec
@schema(#{ name: "MyAPI" })
namespace MyAPI {
  model User {
    id: string;
    name: string;
  }
  @query op getUser(id: string): User;
}
// Emits: MyAPI.graphql

`@specifiedBy`

Provide a specification URL for a custom GraphQL scalar type.
This maps to the @specifiedBy directive in the emitted GraphQL schema.

typespec
@TypeSpec.GraphQL.specifiedBy(url: valueof url)

Target

Scalar

Parameters

Name Type Description
url valueof url URL to the scalar type specification

Examples

typespec
@specifiedBy("https://scalars.graphql.org/andimarek/date-time")
scalar DateTime extends utcDateTime;

`@subscription`

Specify the GraphQL Operation kind for the target operation to be SUBSCRIPTION.

typespec
@TypeSpec.GraphQL.subscription

Target

Operation

Parameters

None

Examples

typespec
@subscription op onUserCreated(): User;

Overview (`packages/html-program-viewer/README.md`)

TypeSpec Html Program viewer emitter.

See also


Overview (`packages/http-canonicalization/README.md`)

@typespec/http-canonicalization

** WARNING: THIS PACKAGE IS EXPERIMENTAL AND WILL CHANGE **

Utilities for emitters and tooling that need to understand type shapes in the HTTP protocol. The canonicalizer produces a mutated type graph specifying shapes in the language and on the wire, as well as groups together relevant HTTP metadata from various compiler APIs in a convenient package.

Why you might use it

  • Get HTTP request and response shapes.
  • Apply visibility transforms.
  • Understand how types are serialized to HTTP.
  • Get tests to determine how to discriminate non-discriminated unions.

Installation

bash
pnpm add @typespec/http-canonicalization

Peer dependencies @typespec/compiler and @typespec/http must be installed.

Quick start

TypeSpec service definition:

typespec
import "@typespec/http";

model Foo {
  @visibility(Lifecycle.Read)
  @encode(DateTimeKnownEncoding.rfc7231)
  createdAt: utcDateTime;

  @visibility(Lifecycle.Create)
  name: string;
}

@route("/foo")
@post
op createFoo(@body foo: Foo): Foo;

Emitter-side usage:

ts
import { $ } from "@typespec/compiler/typekit";
import { HttpCanonicalizer } from "@typespec/http-canonicalization";

const tk = $(program);
const canonicalizer = new HttpCanonicalizer(tk);
const http = canonicalizer.canonicalize(op);
const body = http.requestParameters.body!.type;
// body.type.languageType.name === "FooCreate"
// body.type.visibleProperties => only "name"

const response = http.responses[0];
// response.type.languageType.properties has both "name" and "createdAt"
// response.responses[0].headers?.etag captures header metadata

What the result tells you

  • Models and properties: languageType reflects the shape visible to generated code, while wireType describes the payload sent over the network. Properties removed for the selected visibility remain in properties but carry an intrinsic.never language type so you can detect deletions.
  • Scalars: Each scalar includes a codec (for example rfc7231 for date headers) and may change its wire type (e.g., int32 -> float64) to match HTTP expectations.
  • Operations: Requests expose grouped headers, query, path, and body parameters. Responses list status codes, per-content-type payloads, and derived body information (single, multipart, or file payloads) along with metadata such as content type properties and filenames.
  • Unions: Variant tests contain ordered type guards (primitive checks before object checks) and literal discriminants. If variants cannot be distinguished (for example, two object models with identical shapes), the canonicalizer throws so you can surface a clear error.

API reference

OperationHttpCanonicalization

  • Canonicalizes operations by deriving HTTP-specific request and response shapes and tracks the language and wire projections for each operation.
  • requestParameters: Canonicalized request parameters grouped by location.
  • requestHeaders: Canonicalized header parameters for the request.
  • queryParameters: Canonicalized query parameters for the request.
  • pathParameters: Canonicalized path parameters for the request.
  • responses: Canonicalized responses produced by the operation.
  • path: Concrete path for the HTTP operation.
  • uriTemplate: URI template used for path and query expansion.
  • parameterVisibility: Visibility applied when canonicalizing request parameters.
  • returnTypeVisibility: Visibility applied when canonicalizing response payloads.
  • method: HTTP method verb for the operation.
  • name: Name assigned to the canonicalized operation.
  • languageType: Mutated language type for this operation.
  • wireType: Mutated wire type for this operation.

ModelHttpCanonicalization

  • Canonicalizes models for HTTP and supplies language and wire variants along with visibility-aware metadata.
  • isDeclaration: Indicates if the canonicalization wraps a named TypeSpec declaration.
  • codec: Codec chosen to transform language and wire types for this model.
  • languageType: Possibly mutated language type for the model.
  • wireType: Possibly mutated wire type for the model.
  • visibleProperties: Canonical properties visible under the current visibility options.

ModelPropertyHttpCanonicalization

  • Canonicalizes model properties, tracking HTTP metadata, visibility, and codecs while adjusting types per location.
  • isDeclaration: Indicates if this property corresponds to a named declaration.
  • isVisible: Whether the property is visible with the current visibility options.
  • codec: Codec used to transform the property's type between language and wire views.
  • isQueryParameter: True when the property is a query parameter.
  • queryParameterName: Query parameter name when applicable.
  • isHeader: True when the property is an HTTP header.
  • headerName: Header name when the property is a header.
  • isPathParameter: True when the property is a path parameter.
  • pathParameterName: Path parameter name when applicable.
  • explode: Whether structured values should use explode semantics.
  • languageType: Possibly mutated language type for the property.
  • wireType: Possibly mutated wire type for the property.

ScalarHttpCanonicalization

  • Canonicalizes scalar types by applying encoding-specific mutations driven by codecs.
  • options: Canonicalization options in effect for the scalar.
  • codec: Codec responsible for transforming the scalar into language and wire types.
  • isDeclaration: Indicates whether the scalar is a named TypeSpec declaration.
  • languageType: Possibly mutated language type for the scalar.
  • wireType: Possibly mutated wire type for the scalar.

UnionHttpCanonicalization

  • Canonicalizes union types, tracking discriminators, envelope structures, and runtime variant tests for both language and wire projections.
  • options: Canonicalization options guiding union transformation.
  • isDeclaration: Indicates if the union corresponds to a named declaration.
  • isDiscriminated: True when @discriminator is present on the union.
  • envelopeKind: Envelope structure used for discriminated unions.
  • discriminatorProperty: Canonicalized discriminator property for envelope unions.
  • variantDescriptors: Descriptors describing each canonicalized variant.
  • languageVariantTests: Runtime tests used to select a variant for language types.
  • wireVariantTests: Runtime tests used to select a variant for wire types.
  • discriminatorPropertyName: Name of the discriminator property when present.
  • envelopePropertyName: Name of the envelope property when present.
  • visibleVariants: Variants that remain visible under the current visibility rules.
  • languageType: Potentially mutated language type for this union.
  • wireType: Potentially mutated wire type for this union.

UnionVariantHttpCanonicalization

  • Canonicalizes individual union variants for HTTP, removing hidden variants and exposing mutated representations.
  • options: Canonicalization options.
  • isDeclaration: Indicates if the variant corresponds to a named declaration.
  • isVisible: Whether the variant is visible under the current visibility options.
  • languageType: Possibly mutated language type for this variant.
  • wireType: Possibly mutated wire type for this variant.

IntrinsicHttpCanonicalization

  • Canonicalizes intrinsic types for HTTP, producing language and wire projections directed by the active options.
  • options: Canonicalization options.
  • isDeclaration: Indicates if this intrinsic represents a named declaration.
  • languageType: Possibly mutated language type for this intrinsic.
  • wireType: Possibly mutated wire type for this intrinsic.

LiteralHttpCanonicalization

  • Canonicalizes literal types for HTTP, yielding language and wire variants for string, number, and boolean literals.
  • options: Canonicalization options.
  • isDeclaration: Indicates if the literal is a named declaration (always false for literals).
  • languageType: Possibly mutated language type for this literal.
  • wireType: Possibly mutated wire type for this literal.

Overview (`packages/http-client-csharp/readme.md`)

@typespec/http-client-csharp

TypeSpec library for emitting Http Client libraries for C#.

Install

bash
npm install @typespec/http-client-csharp

Usage

Prerequisite

  • Install Node.js 20 or above. (Verify by running node --version)
  • Install .NET 8.0 SDK for your specific platform. (or a higher version)

Customizing Generated Code

For detailed instructions on how to customize the generated C# code, see the Customization Guide.

Experimental types and members

Use @TypeSpec.HttpClient.experimental to assign a public diagnostic ID to a generated
type or member and identify experiments used by its implementation:

typespec
import "@typespec/http-client";

@TypeSpec.HttpClient.experimental(#{
  emitterScope: "@typespec/http-client-csharp",
  diagnosticId: "C",
  dependsOn: #["A", "B"],
})
op bar(): void;

The C# emitter adds [Experimental("C")] to the corresponding generated declaration:

TypeSpec target Generated C# target
Model Model class or struct
Model property Property
Enum or union emitted as an enum Enum or extensible-enum struct
Enum member or union variant emitted as an enum value Enum field or extensible-enum property
Operation Synchronous and asynchronous protocol and convenience methods
Namespace or interface emitted as a client Client class

For example, models and their properties can be separate experiments:

typespec
@TypeSpec.HttpClient.experimental(#{ diagnosticId: "MODEL001" })
model Preview {
  @TypeSpec.HttpClient.experimental(#{ diagnosticId: "PROPERTY001" })
  value?: string;
}

Model factory methods expose the model's diagnostic. Partial serialization declarations
do not repeat the model attribute. Generated code suppresses diagnostics when referring
to source-annotated experimental types and members, so serialization, factories, and client
implementations can compile without changing the experimental status of other public APIs.

Suppressions surround only the member declarations or serialization blocks that need them,
including parameter and return types, generic arguments, and implementation references.
Explicit type-level dependsOn entries and base-type dependencies can require a type-wide
scope. References within a declaration carrying the same experimental diagnostic ID,
including its other partial declarations, do not need an additional suppression.
These suppressions do not affect diagnostics in consumer code or separate custom files.

Dependencies identify diagnostics rather than individual types: one diagnostic can apply
to multiple types or members, including those defined in external libraries. For externally
mapped types, source @experimental metadata is retained for generated-reference suppressions;
the emitter does not generate the external declaration or add attributes to its library.
This also covers client parameters exposed through generated settings and options properties.
Other external experiments require explicit dependsOn entries. They are not discovered by reflection.

Both metadata fields are optional. Without diagnosticId, no public experimental attribute
is added. Without dependsOn, no additional dependency diagnostics are requested; generated
references to source-annotated experiments are still handled. Emitter scopes apply to both fields.

Diagnostic IDs must be single C# warning identifiers (ASCII letters, digits, and underscores,
not starting with a digit) or decimal warning numbers. Whitespace, punctuation, comments, and
line breaks are rejected with invalid-experimental-diagnostic-id before generating C#.
An ExperimentalAttribute on a customized partial client or method takes precedence over
the generated attribute, including a polyfilled System.Diagnostics.CodeAnalysis.ExperimentalAttribute
on older target frameworks. Generated parent-client accessors suppress a child client's custom
diagnostic without marking the parent or accessor as experimental.

Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions
erased to built-in C# types without an explicit external mapping. C# also does not allow ExperimentalAttribute on parameters.
The emitter reports experimental-target-not-supported for these annotations instead of
silently dropping them or assigning their diagnostic to an unrelated API.

Models referenced by a union retain their own experimental metadata. Annotate the model
declaration, not the union variant that references it: a model variant has no separate C#
declaration on which to place an attribute.

Graduation is an explicit source change: dependencies becoming generally available, or
removing entries from dependsOn, does not remove [Experimental("C")]. Remove the
declaration's @experimental decorator when the public API is ready to graduate.

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-client-csharp
  1. Via the config
yaml
emit:
  - "@typespec/http-client-csharp"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-client-csharp"
options:
  "@typespec/http-client-csharp":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/http-client-csharp
See Configuring output directory for more info

`api-version`

Type: string | object

Use this flag if you would like to generate the sdk only for a specific version. Default value is the latest version. Also accepts values latest and all. For multi-service packages, provide a map from each service namespace to its desired version. Nested namespaces must be represented as nested objects in tspconfig.yaml; services not listed default to their latest version.

Options:

  • string
  • object

`generate-protocol-methods`

Type: boolean

Set to false to skip generation of protocol methods. The default value is true.

`generate-convenience-methods`

Type: boolean

Set to false to skip generation of convenience methods. The default value is true.

`unreferenced-types-handling`

Type: "removeOrInternalize" | "internalize" | "keepAll"

Defines the strategy on how to handle unreferenced types. The default value is removeOrInternalize.

`new-project`

Type: boolean

Set to true to overwrite the csproj if it already exists. The default value is false.

`save-inputs`

Type: boolean

Set to true to save the tspCodeModel.json and Configuration.json files that are emitted and used as inputs to the generator. The default value is false.

`package-name`

Type: string

Define the package name. If not specified, the first namespace defined in the TypeSpec is used as the package name.

`debug`

Type: boolean

Set to true to automatically attempt to attach to a debugger when executing the C# generator. The default value is false.

`logLevel`

Type: "info" | "debug" | "verbose"

Set the log level for which to collect traces. The default value is info.

`disable-xml-docs`

Type: boolean

Set to true to disable XML documentation generation. The default value is false.

`disable-roslyn-reduce`

Type: boolean

Set to true to skip the Roslyn reduce (simplification) post-processing step. This speeds up generation and is useful when iterating quickly. The default value is false.

`generator-name`

Type: string

The name of the generator. By default this is set to ScmCodeModelGenerator. Generator authors can set this to the name of a generator that inherits from ScmCodeModelGenerator.

`emitter-extension-path`

Type: string

Allows emitter authors to specify the path to a custom emitter package, allowing you to extend the emitter behavior. This should be set to import.meta.url if you are using a custom emitter.

`plugins`

Type: string[]

Paths to generator plugin assemblies (DLLs) or directories containing plugin assemblies. Each plugin must contain a class that extends GeneratorPlugin. Paths may be absolute or relative to the resolved emitter-output-dir. For example, to load plugins that live in a codegen folder under the output directory:

yaml
options:
  "@typespec/http-client-csharp":
    plugins:
      - "codegen/MyPlugin.dll" # file relative to emitter-output-dir
      - "codegen" # directory containing plugin assemblies
      - "/abs/path/to/MyPlugin.dll" # absolute path used as-is

`license`

Type: object { name, company, link, header, description }

License information for the generated client code.

Properties:

Name Type Default Description
name string
company string
link string
header string
description string

`sdk-context-options`

Type: object

The SDK context options that implement the CreateSdkContextOptions interface from the @azure-tools/typespec-client-generator-core package to be used by the CSharp emitter.

Decorators

TypeSpec.HttpClient.CSharp

`@dynamicModel`

Marks a model or namespace as dynamic, indicating it should generate dynamic model code.
Can be applied to Model or Namespace types.

typespec
@TypeSpec.HttpClient.CSharp.dynamicModel

Target

Model | Namespace

Parameters

None

Examples

tsp
@dynamicModel
model Pet {
  name: string;
  kind: string;
}

@dynamicModel
namespace PetStore {
  model Dog extends Pet {
    breed: string;
  }
}

Overview (`packages/http-client-java/README.md`)

@typespec/http-client-java

TypeSpec library for emitting Java client from the TypeSpec REST protocol binding

Install

bash
npm install @typespec/http-client-java

Usage

Prerequisite

Install Node.js 20 or above. (Verify by running node --version)

Install Java 17 or above. (Verify by running java --version)

Maven is needed to build the generated client, but not to run the emitter.

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-client-java
  1. Via the config
yaml
emit:
  - "@typespec/http-client-java"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-client-java"
options:
  "@typespec/http-client-java":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/http-client-java
See Configuring output directory for more info

`license`

Type: object

License information for the generated client code.

`dev-options`

Type: object

Developer options for http-client-java emitter.


Overview (`packages/http-client-js/README.md`)

@typespec/http-client-js

TypeSpec library for emitting Http Client libraries for JavaScript/TypeScript

Install

bash
npm install @typespec/http-client-js

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-client-js
  1. Via the config
yaml
emit:
  - "@typespec/http-client-js"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-client-js"
options:
  "@typespec/http-client-js":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/http-client-js
See Configuring output directory for more info

`package-name`

Type: string

Default: "test-package"

Name of the package as it will be in package.json


Overview (`packages/http-client-python/README.md`)

@typespec/http-client-python

TypeSpec emitter for Python SDKs

Install

bash
npm install @typespec/http-client-python

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-client-python
  1. Via the config
yaml
emit:
  - "@typespec/http-client-python"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-client-python"
options:
  "@typespec/http-client-python":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/http-client-python
See Configuring output directory for more info

`api-version`

Type: undefined

Use this flag if you would like to generate the sdk only for a specific version. Default value is the latest version. Also accepts values latest and all. For multi-service packages, provide a map from each service namespace's full name to its desired version; services not listed default to their latest version.

`license`

Type: object

License information for the generated client code.

`package-version`

Type: string

The version of the package.

`package-name`

Type: string

The name of the package.

`generate-packaging-files`

Type: boolean

Whether to generate packaging files. Packaging files refer to the setup.py, README, and other files that are needed to package your code.

`packaging-files-dir`

Type: string

If you are using a custom packaging files directory, you can specify it here. We won't generate with the default packaging files we have.

`packaging-files-config`

Type: object

If you are using a custom packaging files directory, and have additional configuration parameters you want to pass in during generation, you can specify it here. Only applicable if packaging-files-dir is set.

`package-pprint-name`

Type: string

The name of the package to be used in pretty-printing. Will be the name of the package in README and pprinting of setup.py.

`head-as-boolean`

Type: boolean

Whether to return responses from HEAD requests as boolean. Defaults to true.

`enable-sse-reconnect`

Type: boolean

Default: false

Whether to generate automatic reconnection support for Server-Sent Events (SSE). Defaults to false: SSE streams end when the response reaches EOF, without sending another request. Set to true to generate reconnect callbacks, retry delays, and Last-Event-ID resumption. SSE event IDs and retry metadata are parsed in either mode. Transport errors still propagate to the caller.

Enable this generation-time option in tspconfig.yaml:

yaml
emit:
  - "@typespec/http-client-python"
options:
  "@typespec/http-client-python":
    enable-sse-reconnect: true

`use-pyodide`

Type: boolean

Whether to generate using pyodide instead of python. If there is no python installed on your device, we will default to using pyodide to generate the code.

`validate-versioning`

Type: boolean

Whether to validate the versioning of the package. Defaults to true. If set to false, we will not validate the versioning of the package.

`generation-subdir`

Type: string

The subdirectory (relative to the package namespace folder) to generate the code in. Use this to keep emitter-generated code separate from hand-written/customized code, so regeneration only overwrites the subdirectory and leaves your customizations untouched. If not specified, the code is generated directly in the package namespace folder. Note: if you're using this flag, you will need to add and maintain the versioning file (_version.py) yourself.

Example: for namespace: azure.storage.blob with generation-subdir: _generated, generated code lands in azure/storage/blob/_generated/ while your customized code lives in azure/storage/blob/. A typical tspconfig.yaml looks like:

yaml
options:
  "@azure-tools/typespec-python":
    emitter-output-dir: "{output-dir}/{service-dir}/azure-storage-blob"
    namespace: "azure.storage.blob"
    generation-subdir: "_generated"

`keep-setup-py`

Type: boolean

Whether to keep the existing setup.py when generate-packaging-files is true. If set to false and by default, pyproject.toml will be generated instead. To generate setup.py, use basic-setup-py.

`generate-typeddict`

Type: boolean

Whether to add TypedDict typing for JSON dictionary input in models-mode: dpg, instead of accepting only generic JSON. This enriches the typing on the existing overloads rather than adding another request-body overload. Defaults to true.

`keep-pyproject-fields`

Type: object

Which manually customized [project] fields to preserve in an existing pyproject.toml instead of overwriting them on regeneration. Set a field to true to keep it. By default no fields are preserved.

`clear-output-folder`

Type: boolean

Whether to clear the output folder before generating the code. Defaults to false.

`emit-yaml-only`

Type: boolean

Emit YAML code model only, without running Python generator. For batch processing.


Overview (`packages/http-server-csharp/README.md`)

@typespec/http-server-csharp

TypeSpec service code generator for c-sharp

Install

bash
npm install @typespec/http-server-csharp

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-server-csharp
  1. Via the config
yaml
emit:
  - "@typespec/http-server-csharp"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-server-csharp"
options:
  "@typespec/http-server-csharp":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/http-server-csharp
See Configuring output directory for more info

`skip-format`

Type: boolean

Default: false

Skips formatting of generated C# Types. By default, C# files are formatted using 'dotnet format'.

`output-type`

Type: "models" | "all"

Default: "all"

Chooses which service artifacts to emit. choices include 'models' or 'all' artifacts.

`emit-mocks`

Type: "mocks-and-project-files" | "mocks-only" | "none"

Default: "none"

Emits mock implementations of business logic, setup code, and project files, enabling the service to respond to requests before a real implementation is provided

`use-swaggerui`

Type: boolean

Default: false

Configure a Swagger UI endpoint in the development configuration

`openapi-path`

Type: string

Default: null

Use openapi at the given path for generating SwaggerUI endpoints. By default, this will be 'openapi/openapi.yaml' if the 'use-swaggerui' option is enabled.

`overwrite`

Type: boolean

Default: false

When generating mock and project files, overwrite any existing files with the same name.

`project-name`

Type: string

Default: "ServiceProject"

The name of the generated project.

`http-port`

Type: number

Default: null

The service http port when hosting the project locally.

`https-port`

Type: number

Default: null

The service https port when hosting the project locally.

`collection-type`

Type: "array" | "enumerable"

Default: "array"

Specifies the collection type to use: 'array' or 'enumerable'. The default is 'array'.


Overview (`packages/http-server-js/README.md`)

@typespec/http-server-js

:warning: This package is highly experimental and may be subject to breaking changes and bugs. Please expect that your code may need to be updated as this package evolves, and please report any issues you encounter.

TypeSpec HTTP server code generator for JavaScript

Install

bash
npm install @typespec/http-server-js

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-server-js
  1. Via the config
yaml
emit:
  - "@typespec/http-server-js"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-server-js"
options:
  "@typespec/http-server-js":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/http-server-js
See Configuring output directory for more info

`express`

Type: boolean

Default: false

If set to true, the emitter will generate a router that exposes an Express.js middleware function in addition to the ordinary Node.js HTTP server router.

If this option is not set to true, the expressMiddleware property will not be present on the generated router.

`datetime`

Type: "temporal-polyfill" | "temporal" | "date-duration"

Default: "temporal-polyfill"

The type of datetime models to use for TypeSpecs DateTime and Duration types.

`omit-unreachable-types`

Type: boolean

Default: false

By default, the emitter will create interfaces that represent all models in the service namespace. If this option is set to true, the emitter will only emit those types that are reachable from an HTTP operation.

`no-format`

Type: boolean

Default: false

If set to true, the emitter will not format the generated code using Prettier.

Functionality and generated code

The emitter generates a few major components:

Router

The highest-level component that your code interacts with directly is the router implementation.
@typespec/http-server-js generates a static router that you can bind to an implementation of an HTTP server.

The router is generated in the http/router.js module within the output directory. Each service will have its own
router implementation named after the service. For example, given a service namespace named Todo, the router module
will export a function createTodoRouter. This function creates an instance of a router that dispatches methods within
the Todo service.

ts
import { createTodoRouter } from "../tsp-output/@typespec/http-server-js/http/router.js";

const router = createTodoRouter(users, todoItems, attachments);

As arguments, the createTodoRouter function expects implementations of the underlying service interfaces. These
interfaces are explained further in the next section.

Once the router is created, it is bound to an instance of the HTTP server. The router's dispatch method implements the
Node.js event handler signature for the request event on a Node.js HTTP server.

ts
const server = http.createServer();

server.on("request", router.dispatch);

server.listen(8080, () => {
  console.log("Server listening on http://localhost:8080");
});

Alternatively, the router can be used with Express.js instead of the Node.js HTTP server directly. If the express
feature is enabled in the emitter options, the router will expose an expressMiddleware property that implements the
Express.js middleware interface.

ts
import express from "express";

const app = express();

app.use(router.expressMiddleware);

app.listen(8080, () => {
  console.log("Server listening on http://localhost:8080");
});

Service interfaces

The emitter generates interfaces for each collection of service methods that exists in the service namespace.
Implementations of these interfaces are required to instantiate the router. When the router processes an HTTP request,
it will call the appropriate method on the service implementation after determining the route and method.

For example, given the following TypeSpec namespace Users within the Todo service:

tsp
namespace Users {
  @route("/users")
  @post
  op create(user: User): WithStandardErrors<
    | UserCreatedResponse
    | UserExistsResponse
    | InvalidUserResponse>;
}

The emitter will generate a corresponding interface Users within the module models/all/todo/index.js in the output
directory.

ts
/** An interface representing the operations defined in the 'Todo.Users' namespace. */
export interface Users<Context = unknown> {
  create(
    ctx: Context,
    user: User,
  ): Promise<
    | UserCreatedResponse
    | UserExistsResponse
    | InvalidUserResponse
    | Standard4XxResponse
    | Standard5XxResponse
  >;
}

An object implementing this Users interface must be passed to the router when it is created. The Context type
parameter represents the underlying protocol or framework-specific context that the service implementation may inspect.
If you need to access the HTTP request or response objects directly in the implementation of the service methods, you
must use the HttpContext type as the Context argument when implementing the service interface. Otherwise, it is safe
to use the default unknown argument.

ts
import { HttpContext } from "../tsp-output/@typespec/http-server-js/helpers/router.js";
import { Users } from "../tsp-output/@typespec/http-server-js/models/all/todo/index.js";

export const users: Users<HttpContext> = {
  async create(ctx, user) {
    // Implementation
  },
};

Models

The emitter generates TypeScript interfaces that represent the model types used in the service operations. This allows
the service implementation to interact with the data structures carried over the HTTP protocol in a type-safe manner.

Operation functions

While your code should never need to interact with these functions directly, the emitter generates a function per HTTP
operation that handles the parsing and validation of the request contents. This allows the service implementation to be
written in terms of ordinary TypeScript types and values rather than raw HTTP request and response objects. In general:

  • The Node.js HTTP server or Express.js application (your code) calls the router (generated code), which determines
    which service operation function (generated code) to call based on the route, method, and other HTTP metadata in the
    case of shared routes.
  • The operation function (generated code) deserializes the request body, query parameters, and headers into TypeScript
    types, and may perform request validation.
  • The operation function (generated code) calls the service implementation (your code) with the deserialized request
    data.
  • The service implementation (your code) returns a result or throws an error.
  • The operation function (generated code) responds to the HTTP request on your behalf, converting the result or error
    into HTTP response data.

Overview (`packages/http-specs/README.md`)

Http Specs

This package contains all the scenarios that should be supported by a client & service generator.

Development

  1. FOLLOW THE MONOREPO INSTRUCTION to get the environment setup.
  2. Scenarios should be in ./specs folder

Writing scenarios

Docs on writing scenarios specs

Writing mockapis

Docs on writing mock apis

Validate the scenarios are valid

text
pnpm run validate-scenarios

Validate the mock apis are valid

text
pnpm run validate-mock-apis

Start mock api server

This will start the server using the mock apis. When writing a mock api use this command to start the server.

bash
pnpm run serve

Update version for release

bash
pnpm change version --only "@typespec/http-specs"

Push the changes in branch named after the pattern publish/xyz. Once merged, the package will be auto-released.


Overview (`packages/http/README.md`)

@typespec/http

TypeSpec HTTP protocol binding

Install

bash
npm install @typespec/http

Usage

Add the following in tspconfig.yaml:

yaml
linter:
  extends:
    - "@typespec/http/all"

RuleSets

Available ruleSets:

  • @typespec/http/all

Rules

Name Description
@typespec/http/op-reference-container-route Check for referenced (op is) operations which have a @route on one of their containers.

Decorators

TypeSpec.Http

`@body`

Explicitly specify that this property type will be exactly the HTTP body.

This means that any properties under @body cannot be marked as headers, query parameters, or path parameters.
If wanting to change the resolution of the body but still mix parameters, use @bodyRoot.

typespec
@TypeSpec.Http.body

Target

ModelProperty

Parameters

None

Examples

typespec
op upload(@body image: bytes): void;
op download(): {
  @body image: bytes;
};

`@bodyIgnore`

Specify that this property shouldn't be included in the HTTP body.
This can be useful when bundling metadata together that would result in an empty property to be included in the body.

typespec
@TypeSpec.Http.bodyIgnore

Target

ModelProperty

Parameters

None

Examples

typespec
op upload(
  name: string,
  @bodyIgnore headers: {
    @header id: string;
  },
): void;

`@bodyRoot`

Specify that the body resolution should be resolved from that property.
By default the body is resolved by including all properties in the operation request/response that are not metadata.
This allows to nest the body in a property while still allowing to use headers, query parameters, and path parameters in the same model.

typespec
@TypeSpec.Http.bodyRoot

Target

ModelProperty

Parameters

None

Examples

typespec
op upload(
  @bodyRoot user: {
    name: string;
    @header id: string;
  },
): void;
op download(): {
  @bodyRoot user: {
    name: string;
    @header id: string;
  };
};

Specify this property is to be sent or received in the cookie.

typespec
@TypeSpec.Http.cookie(cookieNameOrOptions?: valueof string | TypeSpec.Http.CookieOptions)

Target

ModelProperty

Parameters

Name Type Description
cookieNameOrOptions valueof string | TypeSpec.Http.CookieOptions Optional name of the cookie in the cookie or cookie options.By default the cookie name will be the property name converted from camelCase to snake_case. (e.g. authToken -> auth_token)

Examples

typespec
op read(@cookie token: string): {
  data: string[];
};
op create(
  @cookie({
    name: "auth_token",
  })
  data: string[],
): void;

Implicit header name

typespec
op read(): {
  @cookie authToken: string;
}; // headerName: auth_token
op update(@cookie AuthToken: string): void; // headerName: auth_token

`@delete`

Specify the HTTP verb for the target operation to be DELETE.

typespec
@TypeSpec.Http.delete

Target

Operation

Parameters

None

Examples

typespec
@delete op set(petId: string): void;

`@get`

Specify the HTTP verb for the target operation to be GET.

typespec
@TypeSpec.Http.get

Target

Operation

Parameters

None

Examples

typespec
@get op read(): string;

Specify the HTTP verb for the target operation to be HEAD.

typespec
@TypeSpec.Http.head

Target

Operation

Parameters

None

Examples

typespec
@head op ping(petId: string): void;

Specify this property is to be sent or received as an HTTP header.

typespec
@TypeSpec.Http.header(headerNameOrOptions?: valueof string | TypeSpec.Http.HeaderOptions)

Target

ModelProperty

Parameters

Name Type Description
headerNameOrOptions valueof string | TypeSpec.Http.HeaderOptions Optional name of the header when sent over HTTP or header options.By default the header name will be the property name converted from camelCase to kebab-case. (e.g. contentType -> content-type)

Examples

typespec
op read(@header accept: string): {
  @header("ETag") eTag: string;
};
op create(
  @header({
    name: "X-Color",
    format: "csv",
  })
  colors: string[],
): void;

Implicit header name

typespec
op read(): {
  @header contentType: string;
}; // headerName: content-type
op update(@header ifMatch: string): void; // headerName: if-match

`@multipartBody`

Specify that the target property is the body of a multipart request or response.

The property type must be a model or tuple whose members are all HttpPart, each describing one
part of the payload.

typespec
@TypeSpec.Http.multipartBody

Target

ModelProperty

Parameters

None

Examples

tsp
op upload(
  @header `content-type`: "multipart/form-data",
  @multipartBody body: {
    fullName: HttpPart<string>;
    headShots: HttpPart<Image>[];
  },
): void;

`@patch`

Specify the HTTP verb for the target operation to be PATCH.

typespec
@TypeSpec.Http.patch(options?: valueof TypeSpec.Http.PatchOptions)

Target

Operation

Parameters

Name Type Description
options valueof PatchOptions Options for the PATCH operation.

Examples

typespec
@patch op update(pet: Pet): void;

Using MergePatch template for proper merge-patch semantics

typespec
@patch op update(@body pet: MergePatchUpdate<Pet>): void;

`@path`

Explicitly specify that this property is to be interpolated as a path parameter.

typespec
@TypeSpec.Http.path(paramNameOrOptions?: valueof string | TypeSpec.Http.PathOptions)

Target

ModelProperty

Parameters

Name Type Description
paramNameOrOptions valueof string | TypeSpec.Http.PathOptions Optional name of the parameter in the URI template or options.

Examples

typespec
@route("/read/{explicit}/things/{implicit}")
op read(@path explicit: string, implicit: string): void;

`@post`

Specify the HTTP verb for the target operation to be POST.

typespec
@TypeSpec.Http.post

Target

Operation

Parameters

None

Examples

typespec
@post op create(pet: Pet): void;

`@put`

Specify the HTTP verb for the target operation to be PUT.

typespec
@TypeSpec.Http.put

Target

Operation

Parameters

None

Examples

typespec
@put op set(pet: Pet): void;

`@query`

Specify this property is to be sent as a query parameter.

typespec
@TypeSpec.Http.query(queryNameOrOptions?: valueof string | TypeSpec.Http.QueryOptions)

Target

ModelProperty

Parameters

Name Type Description
queryNameOrOptions valueof string | TypeSpec.Http.QueryOptions Optional name of the query when included in the url or query parameter options.

Examples

typespec
op read(@query select: string, @query("order-by") orderBy: string): void;
op list(@query(#{ name: "id", explode: true }) ids: string[]): void;

`@route`

Defines the relative route URI template for the target operation as defined by RFC 6570

@route can only be applied to operations, namespaces, and interfaces.

typespec
@TypeSpec.Http.route(path: valueof string)

Target

Namespace | Interface | Operation

Parameters

Name Type Description
path valueof string URI template for this operation.

Examples

Simple path parameter

typespec
@route("/widgets/{id}") op getWidget(@path id: string): Widget;

Reserved characters

typespec
@route("/files{+path}") op getFile(@path path: string): bytes;

Query parameter

typespec
@route("/files") op list(select?: string, filter?: string): Files[];
@route("/files{?select,filter}") op listFullUriTemplate(select?: string, filter?: string): Files[];

`@server`

Specify an endpoint for this service. Multiple @server decorators can be used to specify multiple endpoints.

typespec
@TypeSpec.Http.server(url: valueof string, description?: valueof string, parameters?: Record<unknown>)

Target

Namespace

Parameters

Name Type Description
url valueof string Server endpoint
description valueof string Description of the endpoint
parameters Record Optional set of parameters used to interpolate the url.

Examples

typespec
@service
@server("https://example.com")
namespace PetStore;

With a description

typespec
@service
@server("https://example.com", "Single server endpoint")
namespace PetStore;

Parameterized

typespec
@server("https://{region}.foo.com", "Regional endpoint", {
  @doc("Region name")
  region?: string = "westus",
})

Multiple

typespec
@service
@server("https://example.com", "Standard endpoint")
@server(
  "https://{project}.private.example.com",
  "Private project endpoint",
  {
    project: string,
  }
)
namespace PetStore;

`@sharedRoute`

@sharedRoute marks the operation as sharing a route path with other operations.

When an operation is marked with @sharedRoute, it enables other operations to share the same
route path as long as those operations are also marked with @sharedRoute.

@sharedRoute can only be applied directly to operations.

typespec
@sharedRoute
@route("/widgets")
op getWidget(@path id: string): Widget;
typespec
@TypeSpec.Http.sharedRoute

Target

Operation

Parameters

None

`@statusCode`

Specify the status code for this response. Property type must be a status code integer or a union of status code integer.

typespec
@TypeSpec.Http.statusCode

Target

ModelProperty

Parameters

None

Examples

typespec
op read(): {
  @statusCode _: 200;
  @body pet: Pet;
};
op create(): {
  @statusCode _: 201 | 202;
};

`@useAuth`

Specify authentication for a whole service or specific methods. See the documentation in the Http library for full details.

typespec
@TypeSpec.Http.useAuth(auth: {} | Union | {}[])

Target

Namespace | Interface | Operation

Parameters

Name Type Description
auth {} | Union | {}[] Authentication configuration. Can be a single security scheme, a union(either option is valid authentication) or a tuple (must use all authentication together)

Examples

typespec
@service
@useAuth(BasicAuth)
namespace PetStore;

Overview (`packages/json-schema/README.md`)

@typespec/json-schema

TypeSpec library for emitting TypeSpec to JSON Schema and converting JSON Schema to TypeSpec

Install

bash
npm install @typespec/json-schema

Usage

Add the @jsonSchema decorator to any types or namespaces you want to emit as JSON Schema.

typespec
import "@typespec/json-schema";

using JsonSchema;

@jsonSchema
namespace Example;

model Car {
  make: string;
  modelName: string;
}

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/json-schema
  1. Via the config
yaml
emit:
  - "@typespec/json-schema"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/json-schema"
options:
  "@typespec/json-schema":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/json-schema
See Configuring output directory for more info

`file-type`

Type: "yaml" | "json"

Serialize the schema as either yaml or json.

`int64-strategy`

Type: "string" | "number"

How to handle 64 bit integers on the wire. Options are:

  • string: serialize as a string (widely interoperable)
  • number: serialize as a number (not widely interoperable)

`bundleId`

Type: string

When provided, bundle all the schemas into a single json schema document with schemas under $defs. The provided id is the id of the root document and is also used for the file name.

`emitAllModels`

Type: boolean

When true, emit all model declarations to JSON Schema without requiring the @jsonSchema decorator.

`emitAllRefs`

Type: boolean

When true, emit all references as json schema files, even if the referenced type does not have the @jsonSchema decorator or is not within a namespace with the @jsonSchema decorator.

`seal-object-schemas`

Type: boolean

Default: false

If true, then for models emitted as object schemas we default unevaluatedProperties to { not: {} },
if not explicitly specified elsewhere.
Default: false

`polymorphic-models-strategy`

Type: "ignore" | "oneOf" | "anyOf"

Default: "ignore"

Strategy for emitting models with the @discriminator decorator:

  • ignore: Emit as regular object schema (default). Derived models use allOf to reference their base model.
  • oneOf: Emit a oneOf schema with references to all derived models (closed union)
  • anyOf: Emit an anyOf schema with references to all derived models (open union)

When using oneOf or anyOf, derived models will inline all properties from their base model
instead of using allOf references. This avoids circular references in the generated schemas,
since the base model references derived models via oneOf/anyOf.

Decorators

TypeSpec.JsonSchema

`@baseUri`

Set the base URI for any schemas emitted from types within this namespace.

typespec
@TypeSpec.JsonSchema.baseUri(baseUri: valueof string)

Target

Namespace

Parameters

Name Type Description
baseUri valueof string The base URI. Schema IDs inside this namespace are relative to this URI.

`@contains`

Specify that the array must contain at least one instance of the provided type.
Use @minContains and @maxContains to customize how many instances to expect.

typespec
@TypeSpec.JsonSchema.contains(value: unknown)

Target

unknown[] | ModelProperty

Parameters

Name Type Description
value unknown The type the array must contain.

`@contentEncoding`

Specify the encoding used for the contents of a string.

typespec
@TypeSpec.JsonSchema.contentEncoding(value: valueof string)

Target

string | ModelProperty

Parameters

Name Type Description
value valueof string

`@contentMediaType`

Specify the content type of content stored in a string.

typespec
@TypeSpec.JsonSchema.contentMediaType(value: valueof string)

Target

string | ModelProperty

Parameters

Name Type Description
value valueof string The media type of the string contents

`@contentSchema`

Specify the schema for the contents of a string when interpreted according to the content's
media type and encoding.

typespec
@TypeSpec.JsonSchema.contentSchema(value: unknown)

Target

string | ModelProperty

Parameters

Name Type Description
value unknown The schema of the string contents

`@extension`

Specify a custom property to add to the emitted schema. This is useful for adding custom keywords
and other vendor-specific extensions. Scalar values need to be specified using typeof to be converted to a schema.

For example, @extension("x-schema", typeof "foo") will emit a JSON schema value for x-schema,
whereas @extension("x-schema", "foo") will emit the raw code "foo".

The value will be treated as a raw value if any of the following are true:

  • The value is a scalar value (e.g. string, number, boolean, etc.)
  • The value is wrapped in the Json<Data> template
  • The value is provided using the value syntax (e.g. #{}, #[])

For example, @extension("x-schema", { x: "value" }) will emit a JSON schema value for x-schema,
whereas @extension("x-schema", #{x: "value"}) and @extension("x-schema", Json<{x: "value"}>)
will emit the raw JSON code {x: "value"}.

typespec
@TypeSpec.JsonSchema.extension(key: valueof string, value: unknown | valueof unknown)

Target

unknown

Parameters

Name Type Description
key valueof string The name of the keyword of vendor extension, e.g. x-custom.
value unknown | valueof unknown The value of the keyword.

`@id`

Specify the JSON Schema id. If this model or a parent namespace has a base URI,
the provided ID will be relative to that base URI.

By default, the id will be constructed based on the declaration's name.

typespec
@TypeSpec.JsonSchema.id(id: valueof string)

Target

unknown

Parameters

Name Type Description
id valueof string The id of the JSON schema for this declaration.

`@jsonSchema`

Add to namespaces to emit models within that namespace to JSON schema.
Add to another declaration to emit that declaration to JSON schema.

Optionally, for namespaces, you can provide a baseUri, and for other declarations,
you can provide the id.

typespec
@TypeSpec.JsonSchema.jsonSchema(baseUri?: valueof string)

Target

unknown

Parameters

Name Type Description
baseUri valueof string Schema IDs are interpreted as relative to this URI.

`@maxContains`

Used in conjunction with the @contains decorator,
specifies that the array must contain at most a certain number of the types provided by the @contains decorator.

typespec
@TypeSpec.JsonSchema.maxContains(value: valueof int32)

Target

unknown[] | ModelProperty

Parameters

Name Type Description
value valueof int32 The maximum number of instances the array must contain

`@maxProperties`

Specify the maximum number of properties this object can have.

typespec
@TypeSpec.JsonSchema.maxProperties(value: valueof int32)

Target

Record | ModelProperty

Parameters

Name Type Description
value valueof int32 The maximum number of properties this object can have.

`@minContains`

Used in conjunction with the @contains decorator,
specifies that the array must contain at least a certain number of the types provided by the @contains decorator.

typespec
@TypeSpec.JsonSchema.minContains(value: valueof int32)

Target

unknown[] | ModelProperty

Parameters

Name Type Description
value valueof int32 The minimum number of instances the array must contain

`@minProperties`

Specify the minimum number of properties this object can have.

typespec
@TypeSpec.JsonSchema.minProperties(value: valueof int32)

Target

Record | ModelProperty

Parameters

Name Type Description
value valueof int32 The minimum number of properties this object can have.

`@multipleOf`

Specify that the numeric type must be a multiple of some numeric value.

typespec
@TypeSpec.JsonSchema.multipleOf(value: valueof numeric)

Target

numeric | ModelProperty

Parameters

Name Type Description
value valueof numeric The numeric type must be a multiple of this value.

`@oneOf`

Specify that oneOf should be used instead of anyOf for that union.

typespec
@TypeSpec.JsonSchema.oneOf

Target

Union | ModelProperty

Parameters

None

`@prefixItems`

Specify that the target array must begin with the provided types.

typespec
@TypeSpec.JsonSchema.prefixItems(value: unknown[])

Target

unknown[] | ModelProperty

Parameters

Name Type Description
value unknown[] A tuple containing the types that must be present at the start of the array

`@uniqueItems`

Specify that every item in the array must be unique.

typespec
@TypeSpec.JsonSchema.uniqueItems

Target

unknown[] | ModelProperty

Parameters

None


Overview (`packages/library-linter/README.md`)

TypeSpec Library Linter

Installation

Install the package as a dev dependency.

text
npm install -D @typespec/library-linter

Usage

Compile your library package. Any errors or warnings will be reported as TypeSpec diagnostics.

bash
# At the root of your TypeSpec library.
tsp compile . --import @typespec/library-linter

TypeSpec Library Best rules and best practices

Rule name Description
missing-namespace Validate that every exported element from the library (models, JS functions, operations, etc.) is in a namespace.
missing-signature Validate that every exported JS decorator function has a matching extern dec declaration.
missing-documentation Validate that every public declaration and member (properties, enum members, parameters, template parameters) has documentation.
extraneous-documentation Validate that doc comments do not document things that do not exist, such as an unknown @param name or an unrecognized doc tag.

Declarations in a namespace named Private and declarations marked internal are not part of the
public surface of a library and are excluded from the documentation rules.


Overview (`packages/monarch/README.md`)

@typespec/monarch

Provide tokenizer for Monarch for use in monaco-editor.

Updating

Updating the tests

You can copy ./temp/monaco-tests.json(after running the test) into the src/basic-languages/typespec/typespec.test.ts test file in the monaco-editor repository.


Overview (`packages/mutator-framework/readme.md`)

Mutator Framework

WARNING: THIS PACKAGE IS EXPERIMENTAL AND WILL CHANGE

This package provides utilities for building mutations of the TypeSpec type
graph. Mutations are modifications to the original type graph that live in a
parallel type graph and contain additional metadata relevant to consumers of
those mutated types.

At a high level you:

  • Create mutation classes that control how each TypeSpec type (models,
    properties, unions, scalars, literals, operations, etc.) is traversed and
    transformed.
  • Use strongly-typed helper APIs on mutation nodes to mutate into new types or
    traverse to related nodes.
  • Instantiate a MutationEngine subtype (e.g. SimpleMutationEngine) with the
    Typekit from the TypeSpec program you want to mutate.

Key Concepts

Mutations

Mutations are abstract classes that control how each TypeSpec type kind is
traversed and transformed. They have a protocol based on a static mutationInfo
method that returns a unique mutationKey for that mutation. If a mutation with
the same key exists, it is reused; otherwise, a new mutation is constructed.

The mutationInfo method can also return a Mutation directly, useful for
constructing a mutation view "as if" the type graph looked different.

Mutation Nodes

Mutation nodes represent individual type transformations. They are unique per
(type, mutationKey) pair. Connections between nodes are built lazily using
half-edges - mutations call startXEdge() methods to create a MutationHalfEdge
that gets connected when the target mutation is resolved.

Key APIs

  • MutationEngine – orchestrates creation, caching, and traversal of mutations.
  • SimpleMutationEngine – a convenience engine with Simple* mutation classes
    that expose a single mutated type per source type.
  • MutationOptions – lets you parameterize a mutation run and provide a mutationKey.
  • MutationHalfEdge – represents the head-end of a connection; the tail is set when
    the target mutation is resolved.
  • ModelMutation, ModelPropertyMutation, UnionMutation, etc. – abstract base
    classes for crafting custom mutations per TypeSpec kind.
  • SimpleModelMutation, SimpleModelPropertyMutation, etc. – concrete implementations
    that expose mutationNode and mutatedType properties.

Getting Started

ts
import type { Model, Program } from "@typespec/compiler";
import { $ } from "@typespec/compiler/typekit";
import {
  SimpleModelMutation,
  SimpleMutationEngine,
  SimpleMutationOptions,
} from "@typespec/mutator-framework";

// Define custom options with a mutationKey
class RenameMutationOptions extends SimpleMutationOptions {
  constructor(readonly suffix: string) {
    super();
  }

  get mutationKey() {
    return this.suffix;
  }
}

// Define a custom mutation that renames models
class RenameModelMutation extends SimpleModelMutation<RenameMutationOptions> {
  mutate() {
    if ("name" in this.sourceType && typeof this.sourceType.name === "string") {
      this.mutationNode.mutate(
        (type) => (type.name = `${this.sourceType.name}${this.options.suffix}`),
      );
    }
    super.mutate();
  }
}

// Create the engine and run mutations
const tk = $(program);
const engine = new SimpleMutationEngine(tk, {
  Model: RenameModelMutation,
});

const options = new RenameMutationOptions("Suf");
const fooMutation = engine.mutate(fooModel, options);
console.log(fooMutation.mutatedType.name); // "FooSuf"

Defining Custom Mutation Options

Options derive from MutationOptions (or SimpleMutationOptions for simple
mutations). Override the mutationKey getter to provide a unique cache key for
your configuration. Mutations are cached per (type, mutationKey) pair.

ts
import { SimpleMutationOptions } from "@typespec/mutator-framework";

class RenameMutationOptions extends SimpleMutationOptions {
  constructor(readonly suffix: string) {
    super();
  }

  get mutationKey() {
    return this.suffix;
  }

  with(options: Partial<{ suffix: string }>) {
    return new RenameMutationOptions(options.suffix ?? this.suffix);
  }
}

Writing Mutation Classes

Mutation classes are abstract classes that derive from the base classes included
in the framework. Override mutate() to perform your transformations.

The `mutationInfo` Protocol

Each mutation class has a static mutationInfo method that is called to
determine the unique key for a mutation. The default implementation uses
options.mutationKey. Override this to implement context-sensitive mutation
keys based on reference types or other factors:

ts
class RenameModelBasedOnReferenceMutation extends SimpleModelMutation<SimpleMutationOptions> {
  static mutationInfo(
    engine: SimpleMutationEngine<{ Model: RenameModelBasedOnReferenceMutation }>,
    sourceType: Model,
    referenceTypes: MemberType[],
    options: SimpleMutationOptions,
    halfEdge?: MutationHalfEdge,
    traits?: MutationTraits,
  ): MutationInfo {
    // Different key based on whether this type was reached via a reference
    if (referenceTypes.length === 0) {
      return {
        mutationKey: options.mutationKey + "-no-ref",
        hasReference: false,
        isSynthetic: traits?.isSynthetic,
      };
    }
    return {
      mutationKey: options.mutationKey + "-has-ref",
      hasReference: true,
      isSynthetic: traits?.isSynthetic,
    };
  }

  mutate() {
    if (this.mutationInfo.hasReference) {
      this.mutationNode.mutate((type) => (type.name = `${this.sourceType.name}Reference`));
    }
    super.mutate();
  }
}

Lazy Connections with Half-Edges

Connections between mutations are built lazily. Mutations provide startXEdge()
methods that return a MutationHalfEdge. Pass this half-edge to engine.mutate()
or related methods. When the target mutation is resolved, its mutation node is
connected to the originating mutation's node:

ts
class SimpleModelMutation extends ModelMutation {
  // Creates a half-edge for connecting to a property mutation
  startPropertyEdge() {
    return new MutationHalfEdge("property", this, (tail) =>
      this.mutationNode.connectProperty(tail.mutationNode),
    );
  }

  mutate() {
    for (const prop of this.sourceType.properties.values()) {
      // Pass the half-edge so the connection is made when resolved
      this.engine.mutate(prop, this.options, this.startPropertyEdge());
    }
  }
}

Replacing Referenced Types

Use engine.replaceAndMutateReference() to substitute a type with a different
one while preserving the reference chain. This is useful for wrapping types in
unions or other containers:

ts
class UnionifyProperty extends SimpleModelPropertyMutation<SimpleMutationOptions> {
  mutate() {
    if (!this.engine.$.union.is(this.sourceType.type)) {
      // Create a union wrapping the original type
      const newUnionType = this.engine.$.union.create({
        name: "DynamicUnion",
        variants: [
          this.engine.$.unionVariant.create({ type: this.sourceType.type }),
          this.engine.$.unionVariant.create({ type: this.engine.$.builtin.string }),
        ],
      });

      // Update the mutation node
      this.mutationNode.mutate((prop) => {
        prop.type = newUnionType;
      });

      // Replace and mutate the reference
      this.type = this.engine.replaceAndMutateReference(
        this.sourceType,
        newUnionType,
        this.options,
        this.startTypeEdge(),
      );
    } else {
      super.mutate();
    }
  }
}

Returning Mutations from `mutationInfo`

The mutationInfo method can return a Mutation directly instead of a
MutationInfo object. This is useful for completely substituting the mutation
for a different one:

ts
class NullableReferencedModelMutation extends SimpleModelMutation<SimpleMutationOptions> {
  static mutationInfo(
    engine: SimpleMutationEngine<{ Model: NullableReferencedModelMutation }>,
    sourceType: Model,
    referenceTypes: MemberType[],
    options: SimpleMutationOptions,
    halfEdge?: MutationHalfEdge,
    traits?: MutationTraits,
  ) {
    // When accessed via a ModelProperty reference, return a union mutation instead
    if (referenceTypes.length > 0 && referenceTypes[0].kind === "ModelProperty") {
      const nullableUnion = engine.$.union.create({
        name: `${sourceType.name}NullableUnion`,
        variants: [
          engine.$.unionVariant.create({ name: "Value", type: sourceType }),
          engine.$.unionVariant.create({ name: "Null", type: engine.$.intrinsic.null }),
        ],
      });

      // Return a mutation for the union instead
      return engine.replaceAndMutateReference(referenceTypes[0], nullableUnion, options, halfEdge);
    }

    return super.mutationInfo(engine, sourceType, referenceTypes, options, halfEdge, traits);
  }
}

Mutation nodes also have a replace method. Returning a new mutation from MutationInfo makes the resulting Mutations look "as if" the source type graph were shaped differently. This is useful for doing things like normalizations of the type graph. The structure of the Mutations mimic this new structure. When you replace on a mutation node, the Mutation stays the same, but the mutated type graph is changed. This is useful for doing things like renaming things or swapping scalars in situations where you want to see both the source type and the mutated type in order to compare them.

Replacing a member's underlying type from `mutationInfo`

When replaceAndMutateReference is called from a member's mutationInfo (e.g.
a ModelProperty or UnionVariant overriding mutationInfo), the parent's
half-edge expects a member-kind tail (ModelProperty / UnionVariant) but
newType is typically a different kind such as Model, Union, or Scalar.

The engine detects this mismatch (via MutationHalfEdge.expectedTailKind,
declared by the built-in Simple*Mutation member-yielding edges) and wraps
newType in a synthetic member of the expected kind named after the original
reference. The synthetic member's normal mutation flow then mutates newType
recursively through its own type edge, so:

  • parentMutation.properties.get("prop") still returns a ModelProperty
    mutation (the synthetic), keeping the parent's slot kind-correct.
  • propMutation.mutatedType.type points at the mutated newType.
  • Nested replacements (e.g. an alternate model whose own properties are also
    replaced) compose naturally because each level wraps independently.

If the half-edge does not declare expectedTailKind (the default for "type"
edges and any user-defined half-edge), newType is mutated directly through
the half-edge — useful for routing alternate types to a separate edge in
custom engines.

Mutation Caching

Mutations are automatically cached and reused. When you call engine.mutate()
with the same type and options (determined by mutationKey), you get back the
same mutation instance:

ts
const barMutation = engine.mutate(Bar, new RenameMutationOptions({ suffix: "X" }));
const fooMutation = engine.mutate(Foo, new RenameMutationOptions({ suffix: "X" }));

// When traversing from Foo to its Bar property, we get the same mutation
expect(fooMutation.properties.get("prop")!.type === barMutation).toBe(true);

Simple Mutation Classes

The Simple* mutation classes (e.g., SimpleModelMutation, SimpleModelPropertyMutation)
are concrete implementations that provide:

  • A mutationNode property for accessing the underlying mutation node
  • A mutatedType property for accessing the mutated TypeSpec type
  • startXEdge() methods for creating half-edges to connected types

Tips for Building Mutations

  • Always call super.mutate() when you want the default traversal logic after your custom
    changes. Skipping it gives you full control, but you must handle traversal yourself.
  • Use mutationKey to differentiate mutations. Mutations are cached per (type, mutationKey) pair.
  • Inspect referenceTypes in mutationInfo to learn which ModelProperty or UnionVariant
    led to the current mutation. This enables context-sensitive mutations.
  • Use half-edges for lazy connections. Call startXEdge() and pass the result to engine.mutate().
  • Override mutationInfo to return different mutation keys or substitute mutations entirely.
  • Use replaceAndMutateReference when you need to substitute a type with a synthetic one.

Additional Resources

  • Browse the files under packages/mutator-framework/src/mutation to see the built-in
    mutation implementations.
  • The unit tests in simple-mutation-engine.test.ts demonstrate end-to-end usage patterns,
    including custom mutation keys, reference replacements, and type substitutions.

Overview (`packages/openapi/README.md`)

@typespec/openapi

TypeSpec library providing OpenAPI concepts

Install

bash
npm install @typespec/openapi

Decorators

TypeSpec.OpenAPI

`@defaultResponse`

Specify that this model is to be treated as the OpenAPI default response.
This differs from the compiler built-in @error decorator as this does not necessarily represent an error.

typespec
@TypeSpec.OpenAPI.defaultResponse

Target

Model

Parameters

None

Examples

typespec
@defaultResponse
model PetStoreResponse is object;

op listPets(): Pet[] | PetStoreResponse;

`@extension`

Attach some custom data to the OpenAPI element generated from this type.

typespec
@TypeSpec.OpenAPI.extension(key: valueof string, value: valueof unknown)

Target

unknown

Parameters

Name Type Description
key valueof string Extension key.
value valueof unknown Extension value.

Examples

typespec
@extension("x-custom", "My value")
@extension("x-pageable", #{ nextLink: "x-next-link" })
op read(): string;

`@externalDocs`

Specify the OpenAPI externalDocs property for this type.

typespec
@TypeSpec.OpenAPI.externalDocs(url: valueof string, description?: valueof string)

Target

unknown

Parameters

Name Type Description
url valueof string Url to the docs
description valueof string Description of the docs

Examples

typespec
@externalDocs(
  "https://example.com/detailed.md",
  "Detailed information on how to use this operation"
)
op listPets(): Pet[];

`@info`

Specify OpenAPI additional information.
The service title is already specified using @service.

typespec
@TypeSpec.OpenAPI.info(additionalInfo: valueof TypeSpec.OpenAPI.AdditionalInfo)

Target

Namespace

Parameters

Name Type Description
additionalInfo valueof AdditionalInfo Additional information

`@operationId`

Specify the OpenAPI operationId property for this operation.

typespec
@TypeSpec.OpenAPI.operationId(operationId: valueof string)

Target

Operation

Parameters

Name Type Description
operationId valueof string Operation id value.

Examples

typespec
@operationId("download")
op read(): string;

`@tagMetadata`

Specify OpenAPI tag metadata. Can be used in two forms:

  • Inline form: specify a single tag by name with optional metadata.
  • Array form: specify an ordered list of tags with their metadata in a single decorator call.
typespec
@TypeSpec.OpenAPI.tagMetadata(name: valueof string | TypeSpec.OpenAPI.TagMetadataWithName[], tagMetadata?: valueof TypeSpec.OpenAPI.TagMetadata)

Target

Namespace

Parameters

Name Type Description
name valueof string | TypeSpec.OpenAPI.TagMetadataWithName[] Tag name (inline form) or array of tags with metadata (array form).
tagMetadata valueof TagMetadata Additional information for the tag. Only used in inline form.

Examples

Inline form

typespec
@service
@tagMetadata(
  "Tag Name",
  #{
    description: "Tag description",
    externalDocs: #{ url: "https://example.com", description: "More info.", `x-custom`: "string" },
    `x-custom`: "string",
  }
)
@tagMetadata("Child Tag", #{ description: "Child tag description", parent: "Tag Name" })
namespace PetStore {

}

Array form (preserves explicit tag order)

typespec
@service
@tagMetadata(#[
  #{ name: "First Tag", description: "First tag description" },
  #{ name: "Second Tag", description: "Second tag description" }
])
namespace PetStore {

}

Overview (`packages/openapi3/README.md`)

@typespec/openapi3

TypeSpec library for emitting OpenAPI 3.0 and OpenAPI 3.1 from the TypeSpec REST protocol binding and converting OpenAPI3 to TypeSpec

Install

bash
npm install @typespec/openapi3

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/openapi3
  1. Via the config
yaml
emit:
  - "@typespec/openapi3"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/openapi3"
options:
  "@typespec/openapi3":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/openapi3
See Configuring output directory for more info

`file-type`

Type: "yaml" | "json" | ("yaml" | "json")[]

If the content should be serialized as YAML or JSON. Can be a single value or an array to emit multiple formats. Default 'yaml', if not specified infer from the output-file extension

Options:

  • "yaml" | "json"
  • ("yaml" | "json")[]

`output-file`

Type: string

Name of the output file.
Output file will interpolate the following values:

  • service-name: Name of the service
  • service-name-if-multiple: Name of the service if multiple
  • version: Version of the service if multiple
  • file-type: The file type being emitted (json or yaml). Useful when file-type is an array.

Default: {service-name-if-multiple}.{version}.openapi.yaml or .json if file-type is "json"
When file-type is an array: {service-name-if-multiple}.{version}.openapi.{file-type}

Example Single service no versioning

  • openapi.yaml

Example Multiple services no versioning

  • openapi.Org1.Service1.yaml
  • openapi.Org1.Service2.yaml

Example Single service with versioning

  • openapi.v1.yaml
  • openapi.v2.yaml

Example Multiple service with versioning

  • openapi.Org1.Service1.v1.yaml
  • openapi.Org1.Service1.v2.yaml
  • openapi.Org1.Service2.v1.0.yaml
  • openapi.Org1.Service2.v1.1.yaml

`openapi-versions`

Type: "3.0.0" | "3.1.0" | "3.2.0"

Default: ["3.0.0"]

`new-line`

Type: "crlf" | "lf"

Default: "lf"

Set the newline character for emitting files.

`omit-unreachable-types`

Type: boolean

Omit unreachable types.
By default all types declared under the service namespace will be included. With this flag on only types references in an operation will be emitted.

`include-x-typespec-name`

Type: "inline-only" | "never"

Default: "never"

If the generated openapi types should have the x-typespec-name extension set with the name of the TypeSpec type that created it.
This extension is meant for debugging and should not be depended on.

`safeint-strategy`

Type: "double-int" | "int64"

Default: "int64"

How to handle safeint type. Options are:

  • double-int: Will produce type: integer, format: double-int
  • int64: Will produce type: integer, format: int64

Default: int64

`seal-object-schemas`

Type: boolean

Default: false

If true, then for models emitted as object schemas we default additionalProperties to false for
OpenAPI 3.0, and unevaluatedProperties to false for OpenAPI 3.1, if not explicitly specified elsewhere.
Default: false

`experimental-parameter-examples`

Type: "data" | "serialized"

Determines how to emit examples on parameters.
Note: This is an experimental feature and may change in future versions.
See https://spec.openapis.org/oas/v3.0.4.html#style-examples for parameter example serialization rules
See https://github.com/OAI/OpenAPI-Specification/discussions/4622 for discussion on handling parameter examples.

`operation-id-strategy`

Type: "parent-container" | "fqn" | "explicit-only" | object { kind, separator }

Options:

  • "parent-container" | "fqn" | "explicit-only" (default: "parent-container")

    Determines how to generate operation IDs when @operationId is not used.
    Avaliable options are:

  • parent-container: Uses the parent namespace and operation name to generate the ID.

  • fqn: Uses the fully qualified name of the operation to generate the ID.

  • explicit-only: Only use explicitly defined operation IDs.

  • object { kind, separator }

Name Type Default Description
kind "parent-container" | "fqn" | "explicit-only" "parent-container" Determines how to generate operation IDs when @operationId is not used.Avaliable options are: - parent-container: Uses the parent namespace and operation name to generate the ID. - fqn: Uses the fully qualified name of the operation to generate the ID. - explicit-only: Only use explicitly defined operation IDs.
separator string Separator used to join segment in the operation name.

`enum-strategy`

Type: "default" | "annotated"

Default: "default"

How to emit TypeSpec enums and unions of literals. Options are:

  • default: Emit as a single schema using the enum keyword.
  • annotated: Emit as a oneOf of const subschemas annotated with title and description
    from each member's/variant's @summary and @doc. Follows the OpenAPI 3.1.1 annotated enumerations pattern.
    Only supported by OpenAPI 3.1.0 and above; on 3.0.0 the default style is used and a warning is reported.

Decorators

TypeSpec.OpenAPI

`@oneOf`

Specify that oneOf should be used instead of anyOf for that union.

typespec
@TypeSpec.OpenAPI.oneOf

Target

Union | ModelProperty

Parameters

None

`@useRef`

Specify an external reference that should be used inside of emitting this type.

typespec
@TypeSpec.OpenAPI.useRef(ref: valueof string)

Target

Model | ModelProperty

Parameters

Name Type Description
ref valueof string External reference(e.g. "../../common.json#/components/schemas/Foo")

Overview (`packages/playground-website/README.md`)

TypeSpec Playground Website

A self contained website for the TypeSpec Playground.

Use

  • npm start to start in dev mode
  • npm run preview to build and preview prod mode
  • npm run build to build for production

Configuration

To change the default configuration add a .env.local file in the root of this package.

The following environment variables are available:

.env
# Bundle and use local version of TypeSpec libraries
VITE_USE_LOCAL_LIBRARIES=true

Overview (`packages/playground/README.md`)

TypeSpec Playground

Contains react components for the TypeSpec playground.

It can be used as a standalone custom playground with your own libraries or components can be used individually to customize the UI as you see fit.

Usage

Standalone

The stanalone playground provides some vite helpers to make it easy to get started.

In vite.config.ts:

ts
import { definePlaygroundViteConfig } from "@typespec/playground/vite";

const config = definePlaygroundViteConfig({
  defaultEmitter: "@typespec/openapi3",
  libraries: [
    "@typespec/compiler",
    "@typespec/http",
    "@typespec/openapi3",

    // Add any other libraries here. Make sure those libraries are also dependencies of that package.
  ],
  samples: {
    "My sample": {
      filename: "samples/my.tsp",
      preferredEmitter: "@typespec/openapi3",
    },
  },
  links: {
    documentationUrl: "<link to your website>",
  },
});

export default config;

In src/main.tsx:

tsx
import { registerMonacoDefaultWorkersForVite } from "@typespec/playground";
import PlaygroundManifest from "@typespec/playground/manifest";
import { renderReactPlayground } from "@typespec/playground/react";
import { SwaggerUIViewer } from "@typespec/playground/react/viewers";

// Import styles
import "@typespec/playground/styles.css";

registerMonacoDefaultWorkersForVite();

await renderReactPlayground({
  ...PlaygroundManifest,
  emitterViewers: {
    "@typespec/openapi3": [SwaggerUIViewer],
  },
});

Individual components

Playground react components can be used individually. For the TypeSpec compiler to be working correctly, the following conditions need to be met:


Overview (`packages/prettier-plugin-typespec/README.md`)

Prettier Plugin for TypeSpec

Requirements

  • Using Node 14 and above
  • For use in VS Code, use version VS Code 1.56 and above.

Getting Started

bash
npm install --save-dev prettier @typespec/prettier-plugin-typespec

You can now call prettier

bash
./node_modules/.bin/prettier --write '**/*.tsp'

Overview (`packages/protobuf/README.md`)

@typespec/protobuf

TypeSpec library and emitter for Protobuf (gRPC)

Install

bash
npm install @typespec/protobuf

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/protobuf
  1. Via the config
yaml
emit:
  - "@typespec/protobuf"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/protobuf"
options:
  "@typespec/protobuf":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/protobuf
See Configuring output directory for more info

`noEmit`

Type: boolean

If set to true, this emitter will not write any files. It will still validate the TypeSpec sources to ensure they are compatible with Protobuf, but the files will simply not be written to the output directory.

`omit-unreachable-types`

Type: boolean

By default, the emitter will create message declarations for any models in a namespace decorated with @package that have an @field decorator on every property. If this option is set to true, this behavior will be disabled, and only messages that are explicitly decorated with @message or that are reachable from a service operation will be emitted.

`enum-value-prefix`

Type: "none" | "enum-name"

Default: "none"

When set to enum-name, enum values are prefixed with the enum name in UPPER_SNAKE_CASE. Already-prefixed names are preserved. By default (none), member names are emitted unchanged. This option only changes names; explicit integer values and a first member set to zero are still required.

Decorators

TypeSpec.Protobuf

`@field`

Defines the field index of a model property or union variant for conversion to a Protobuf
message.

When applied to the variants of a named union, the union can be used as the type of a message field:

  • If the model property has no @field decorator, the union is emitted inline as a oneof named after the
    property, and the variant field indices share the field index space of the containing message. The property
    must be optional.
  • If the model property has a @field decorator, the union is emitted as a wrapper message (named after the
    union) that contains a oneof value, and the variant field indices belong to the wrapper message.

Union variants used in a oneof cannot be arrays or maps.

The field index of a Protobuf message must:

  • fall between 1 and 229 - 1, inclusive.
  • not fall within the implementation reserved range of 19000 to 19999, inclusive.
  • not fall within any range that was marked reserved.
  • not be used by any other field of the same message, including members of a oneof.

API Compatibility Note

Fields are accessed by index, so changing the index of a field is an API breaking change.

Encoding

Field indices between 1 and 15 are encoded using a single byte, while field indices from 16 through 2047 require two
bytes, so those indices between 1 and 15 should be preferred and reserved for elements that are frequently or always
set in the message. See the Protobuf binary format.

typespec
@TypeSpec.Protobuf.field(index: valueof uint32)

Target

ModelProperty | UnionVariant

Parameters

Name Type Description
index valueof uint32 The whole-number index of the field.

Examples

typespec
model ExampleMessage {
  @field(1)
  test: string;
}
typespec
union Payment {
  @field(10) card: CardPayment,
  @field(11) bank_transfer: BankTransfer,
}

model Order {
  @field(1) id: string;

  // Emitted inline as `oneof payment { CardPayment card = 10; BankTransfer bank_transfer = 11; }`
  payment?: Payment;
}

`@message`

Declares that a model is a Protobuf message.

Messages can be detected automatically if either of the following two conditions are met:

  • The model has a @field annotation on all of its properties.
  • The model is referenced by any service operation.

This decorator will force the emitter to check and emit a model. A named union annotated with this decorator is
emitted as a wrapper message containing a oneof value.

typespec
@TypeSpec.Protobuf.message

Target

{}

Parameters

None

`@package`

Declares that a TypeSpec namespace constitutes a Protobuf package. The contents of the namespace will be emitted to a
single Protobuf file.

typespec
@TypeSpec.Protobuf.package(details?: TypeSpec.Protobuf.PackageDetails)

Target

Namespace

Parameters

Name Type Description
details PackageDetails the optional details of the package

`@reserve`

Reserve a field index, range, or name. If a field definition collides with a reservation, the emitter will produce
an error.

This decorator accepts multiple reservations. Each reservation is one of the following:

  • a string, in which case the reservation refers to a field name.
  • a uint32, in which case the reservation refers to a field index.
  • a tuple [uint32, uint32], in which case the reservation refers to a field range that is inclusive of both ends.

Unlike in Protobuf, where field name and index reservations must be separated, you can mix string and numeric field
reservations in a single @reserve call in TypeSpec.

API Compatibility Note

Field reservations prevent users of your Protobuf specification from using the given field names or indices. This can
be useful if a field is removed, as it will further prevent adding a new, incompatible field and will prevent users
from utilizing the field index at runtime in a way that may break compatibility with users of older specifications.

See Protobuf Language Guide - Reserved Fields for more
information.

typespec
@TypeSpec.Protobuf.reserve(...reservations: valueof string | [uint32, uint32] | uint32[])

Target

{}

Parameters

Name Type Description
reservations valueof string | [uint32, uint32] | uint32[] a list of field reservations

Examples

typespec
// Reserve the fields 8-15 inclusive, 100, and the field name "test" within a model.
@reserve([8, 15], 100, "test")
model Example {
  // ...
}

`@service`

Declares that a TypeSpec interface constitutes a Protobuf service. The contents of the interface will be converted to
a service declaration in the resulting Protobuf file.

typespec
@TypeSpec.Protobuf.service

Target

Interface

Parameters

None

`@stream`

Set the streaming mode of an operation. See StreamMode for more information.

typespec
@TypeSpec.Protobuf.stream(mode: TypeSpec.Protobuf.StreamMode)

Target

Operation

Parameters

Name Type Description
mode StreamMode The streaming mode to apply to this operation.

Examples

typespec
@stream(StreamMode.Out)
op logs(...LogsRequest): LogEvent;
typespec
@stream(StreamMode.Duplex)
op connectToMessageService(...Message): Message;

Overview (`packages/react-components/README.md`)

Set of components that are used in different UI packages of TypeSpec


Overview (`packages/rest/README.md`)

@typespec/rest

TypeSpec REST protocol binding

Install

bash
npm install @typespec/rest

Decorators

TypeSpec.Rest

`@action`

Specify this operation is an action. (Scoped to a resource item /pets/{petId}/my-action)

typespec
@TypeSpec.Rest.action(name?: valueof string)

Target

Operation

Parameters

Name Type Description
name valueof string Name of the action. If not specified, the name of the operation will be used.

`@actionSeparator`

Defines the separator string that is inserted before the action name in auto-generated routes for actions.

When applied to a namespace, the separator applies to all action operations in that namespace and its sub-namespaces.
When applied to an interface, the separator applies to all action operations in that interface and overrides any namespace-level separator.
When applied to an operation, the separator applies only to that operation and overrides any interface or namespace-level separator.

typespec
@TypeSpec.Rest.actionSeparator(seperator: valueof "/" | ":" | "/:")

Target

Operation | Interface | Namespace

Parameters

Name Type Description
seperator valueof "/" | ":" | "/:" Seperator seperating the action segment from the rest of the url

`@autoRoute`

This interface or operation should resolve its route automatically. To be used with resource types where the route segments area defined on the models.

typespec
@TypeSpec.Rest.autoRoute

Target

Interface | Operation

Parameters

None

Examples

typespec
@autoRoute
interface Pets {
  get(@segment("pets") @path id: string): void; //-> route: /pets/{id}
}

`@collectionAction`

Specify this operation is a collection action. (Scopped to a resource, /pets/my-action)

typespec
@TypeSpec.Rest.collectionAction(resourceType: Model, name?: valueof string)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource
name valueof string Name of the action. If not specified, the name of the operation will be used.

`@copyResourceKeyParameters`

Copy the resource key parameters on the model

typespec
@TypeSpec.Rest.copyResourceKeyParameters(filter?: valueof string)

Target

Model

Parameters

Name Type Description
filter valueof string Filter to exclude certain properties.

`@createsOrReplacesResource`

Specify that this is a CreateOrReplace operation for a given resource.

typespec
@TypeSpec.Rest.createsOrReplacesResource(resourceType: Model)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource

`@createsOrUpdatesResource`

Specify that this is a CreatesOrUpdate operation for a given resource.

typespec
@TypeSpec.Rest.createsOrUpdatesResource(resourceType: Model)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource

`@createsResource`

Specify that this is a Create operation for a given resource.

typespec
@TypeSpec.Rest.createsResource(resourceType: Model)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource

`@deletesResource`

Specify that this is a Delete operation for a given resource.

typespec
@TypeSpec.Rest.deletesResource(resourceType: Model)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource

`@listsResource`

Specify that this is a List operation for a given resource.

typespec
@TypeSpec.Rest.listsResource(resourceType: Model)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource

`@parentResource`

Mark model as a child of the given parent resource.

typespec
@TypeSpec.Rest.parentResource(parent: Model)

Target

Model

Parameters

Name Type Description
parent Model Parent model.

`@readsResource`

Specify that this is a Read operation for a given resource.

typespec
@TypeSpec.Rest.readsResource(resourceType: Model)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource

`@resource`

Mark this model as a resource type with a name.

typespec
@TypeSpec.Rest.resource(collectionName: valueof string)

Target

Model

Parameters

Name Type Description
collectionName valueof string type's collection name

`@segment`

Defines the preceding path segment for a @path parameter in auto-generated routes.

typespec
@TypeSpec.Rest.segment(name: valueof string)

Target

Model | ModelProperty | Operation

Parameters

Name Type Description
name valueof string Segment that will be inserted into the operation route before the path parameter's name field.

Examples

typespec
@autoRoute
interface Pets {
  get(@segment("pets") @path id: string): void; //-> route: /pets/{id}
}

`@segmentOf`

Returns the URL segment of a given model if it has @segment and @key decorator.

typespec
@TypeSpec.Rest.segmentOf(type: Model)

Target

Operation

Parameters

Name Type Description
type Model Target model

`@updatesResource`

Specify that this is a Update operation for a given resource.

typespec
@TypeSpec.Rest.updatesResource(resourceType: Model)

Target

Operation

Parameters

Name Type Description
resourceType Model Resource marked with @resource

Overview (`packages/samples/README.md`)

TypeSpec Samples

This project has a collection of samples used to demonstrate and test various TypeSpec features.

It is not published as an npm package.

Samples should teach a distinct TypeSpec concept or realistic workflow. Narrow protocol test
cases belong in the relevant package test suite instead of this collection.

Each sample directory has a sample-config.yaml containing a unique title and a
description. Set playground: false for samples that require multiple files or are not suitable
for the playground. A directory config uses directory: true and can provide a gallery label,
danger, or an inherited playground: false.

The TypeSpec playground is generated from the eligible samples in this package. A playground
sample must contain a single main.tsp; its nearest tspconfig.yaml selects the preferred emitter
and linter rule set, including inherited configuration.

bash
npm run test    # Check Samples match snapshots
npm run test:ci # run test same as CI

npm run test:regen -- -g "<sample-name>" # Regen of this name

npm run regen-samples # Regen all samples.

Overview (`packages/spec-dashboard/README.md`)

Spector Dashboard

Coverage overview groups

Use groupEmitters to combine emitters into named overview cards:

tsx
<Dashboard
  coverageSummaries={coverageSummaries}
  showOverview
  groupEmitters={[
    {
      name: "C#",
      emitters: ["@azure-typespec/http-client-csharp", "@azure-typespec/http-client-csharp-mgmt"],
    },
  ]}
  emitterDisplayNames={{
    "@azure-typespec/http-client-csharp": "C# (Data plane)",
    "@azure-typespec/http-client-csharp-mgmt": "C# (Management plane)",
  }}
/>

Each card shows the union of its emitters' coverage, with individual percentages underneath.
A scenario counts once per table and is covered if any member reports pass, not-applicable,
or not-supported. Two emitters each covering 50 of 100 scenarios, with 25 shared successes,
produce 75% combined coverage. Counts follow the selected tier and use each table's reports;
equally named scenarios in different tables/spec sets remain separate.

Groups and emitter rows follow the configured order; ungrouped emitters keep separate cards
afterward. emitterDisplayNames controls row and table labels without affecting grouping.
These options are also available on DashboardFromAzureStorage's options and do not change
table columns or report loading.

Dev

bash
npm run start
# or
npm run dev

Show the test generator

Add ?showtest=true query parameter to url http://localhost:5173/?showtest=true


Overview (`packages/spec/README.md`)

TypeSpec Specification Source

This project builds the TypeSpec Language Specification.

It is not published as an npm package.


Overview (`packages/spector/README.md`)

Spector

Spector is a tool and typespec library used to describe spec for various protocols and running mock server and mock client based on these specs and some mock apis data.

Scenario project features

Scenario compilation and mock API validation honor compiler feature flags in the scenario's project configuration. For example, place this tspconfig.yaml next to a scenario using union extends:

yaml
kind: project
features:
  - union-extends

Configuration errors are reported alongside TypeSpec diagnostics. Spector continues to compile without emitting code and treats compiler warnings as errors; emitter and linter settings do not change its validation behavior.

Commands

`serve`

bash
tsp serve ./path/to/specs/
tsp serve ./path/to/specs/ --port 3000

`server start` and `server stop`

Start the server and stop the server in a separate process.

bash
tsp server start ./path/to/specs/
... run tests ...
tsp server stop

`knock`

bash
tsp knock ./path/to/specs/
tsp knock ./path/to/specs/ --baseUrl http://localhost:3000
tsp knock ./path/to/specs/ --filter "payload/multipart/**/*"

Options:

Option Description Default
--baseUrl Base url for the server http://localhost:3000
--filter Filters for which scenarios to run(glob)

Overview (`packages/sse/README.md`)

@typespec/sse

TypeSpec library providing server sent events bindings

Install

bash
npm install @typespec/sse

Decorators

TypeSpec.SSE

`@terminalEvent`

Indicates that the presence of this event is a terminal event,
and the client should disconnect from the server.

typespec
@TypeSpec.SSE.terminalEvent

Target

UnionVariant

Parameters

None


Overview (`packages/standalone/README.md`)

TypeSpec CLI Standalone Package

This package contains the logic for building and bundling the TypeSpec CLI as a standalone executable.

It requires Node.js 25.5+ to build: the executable is produced in one step with
node --build-sea. This package only
targets the latest Node.js — supporting older versions is a non-goal. The end user runs the
self-contained executable and does not need any Node.js version installed.

Bundled compiler

The executable ships with a pinned @typespec/compiler (the version built alongside it)
embedded directly inside the single-executable. The compiler's tsp init templates are embedded as
executable assets and served from memory, so running commands such as tsp init outside of an
installed project needs no network access, no package manager, and no files written to disk.

The standard library is not bundled. The bundled compiler is a bootstrapper: it runs init,
--version, --help, and format offline, while compile always uses a project-local
@typespec/compiler. Running tsp compile with no local compiler installed fails with guidance to
run tsp install / tsp init first.

Compiler resolution at runtime:

  1. --server / TYPESPEC_COMPILER_PATH — used if provided.
  2. A @typespec/compiler resolvable from the current working directory (a project-local install)
    always takes precedence over the bundled one.
  3. Otherwise the pinned compiler bundled into the executable is used.

To upgrade the compiler used outside of a project, install a newer executable; there is no runtime
self-update.

Implementation notes: long-term design vs. temporary workarounds

Long-term design (inherent to shipping a self-contained executable):

  • The bundled compiler is a bootstrapper: it bundles only the tsp init templates, not the
    standard library. src/cli.ts (requireLocalCompilerForCompile) fails fast with actionable
    guidance if compile is run with no project-local compiler installed.
  • The tsp init templates are embedded as a single-executable asset (a map of template-relative
    path to file contents). compiler-entry.ts builds an InMemoryTemplateSource from that asset and
    passes it to the CLI via runTypeSpecCli({ coreTemplateSource }) (from
    @typespec/compiler/internals/standalone), so init serves templates fully from memory while
    everything else uses the real NodeHost. No global state is shared between the CJS and ESM
    entrypoints, and the compiler's host contract (getExecutionRoot) is left untouched.
  • In-memory module serving via module.registerHooks (ensureHooks/serveFromMemory in
    src/module-loader.ts): SEA assets are strings in memory, so hooks map synthetic URLs to those
    sources.
  • The compiler is bundled as a separate ESM asset because it uses top-level await, which a
    CommonJS single-executable main cannot.

Temporary workarounds (remove when the linked upstream issue is fixed — each is tagged with a
WORKAROUND(...) comment in the code):

Where Issue What / removal
src/import-workaround.ts (the createRequire bridge, importExternal) nodejs/node#62726 A SEA main can only import builtin modules. Once fixed, delete this file and replace importExternal(specifier) calls with import(specifier).

Overview (`packages/streams/README.md`)

@typespec/streams

TypeSpec library providing stream bindings

Install

bash
npm install @typespec/streams

Decorators

TypeSpec.Streams

`@streamOf`

Specify that a model represents a stream protocol type whose data is described
by Type.

typespec
@TypeSpec.Streams.streamOf(type: unknown)

Target

Model

Parameters

Name Type Description
type unknown The type that models the underlying data of the stream.

Examples

typespec
model Message {
  id: string;
  text: string;
}

@streamOf(Message)
model Response {
  @body body: string;
}

Overview (`packages/tmlanguage-generator/README.md`)

TMLanguage Generator

This package provides a helper library for authoring TextMate grammars using TypeScript.

Usage Examples


Overview (`packages/tspd/README.md`)

@typespec/tspd

This library is experimental and will most likely significantly change in future versions.

TypeSpec tspd is a tool to help you build TypeSpec libraries and emitters. It provide the ability to

  • Generate decorator signatures and type checks
  • Generate documentation for library types and emitter options

Usage

bash
tspd --enable-experimental gen-extern-signature
bash
tspd --enable-experimental doc . --output-dir ./docs/

Sub path exports

gen-extern-signature generates signatures for every entry of the exports field in package.json that defines a typespec condition.

Each export is compiled on its own and a decorator or function is attributed to the first export that reaches the file declaring it. This means entities declared in the root entrypoint stay on the root even when a sub path entrypoint imports it back.

Signatures for the root export(.) are written to generated-defs/, signatures for a sub path are written to a matching directory and import $decorators/$functions from that same sub path:

text
generated-defs/MyLib.ts                  <- from `.`,          imports from "my-lib"
generated-defs/streams/MyLib.Streams.ts  <- from `./streams`,  imports from "my-lib/streams"

For this to work the sub path must expose its own JS module (an import or default condition) exporting the $decorators of the decorators it declares, and its TypeSpec entrypoint must import that JS module.


Overview (`packages/typespec-vs/README.md`)

TypeSpec Language Support for Visual Studio

Repository | Documentation | Issues | Samples

This provides provides TypeSpec language support for Visual Studio.

Features

  • Live diagnostic reporting
  • Syntax highlighting
  • Code completion
  • Code folding
  • Formatting
  • Hover info
  • Rename refactoring
  • Go to definition

Configure TypeSpec Visual Studio Extension

  1. Create a file .vs/VSWorkspaceSettings.json at the root of the project.
  2. Add configuration as key value pair in this file. Example:
json
{
  "typespec.tsp-server.path": "${workspaceFolder}/my-nested-project/node_modules/@typespec/compiler"
}

TypeSpec wil interpolate a few variables using this pattern ${<name>}. For example ${workspaceFolder}.

Available variables:

  • workspaceFolder: Correspond to the root of your Visual Studio workspace.

Overview (`packages/typespec-vscode/README.md`)

TypeSpec Extension in Visual Studio Code

Repository | Documentation | Issues | Samples

The TypeSpec extension for Visual Studio Code enhances the development of TypeSpec by leveraging VS Code's powerful features. It provides:

  • IntelliSense and syntax highlighting
  • Code autocompletion and formatting
  • Live diagnostics and quick fixes
  • Refactoring tools (rename, go-to definition, etc.)
  • Seamless project setup and emitter configuration <_new_>
  • Import TypeSpec from existing OpenAPI 3 definitions <_new_>
  • Emit code from TypeSpec <_new_>
  • Preview API documentation <_new_>

Prerequisites

Before using the TypeSpec extension, install Node.js and verify npm is available:

sh
npm --version

Install the TypeSpec CLI:

sh
npm install @typespec/compiler

Other necessary installations will be prompted within the extension as needed.

Features

Write TypeSpec

  • IntelliSense & Auto-completion: Code faster with smart suggestions.
  • Code Formatting & Folding: Keep your code clean and organized with built-in formatting and folding support.
  • Syntax Highlighting: Clear and readable TypeSpec syntax.
  • Live Diagnostics: Get real-time feedback on code issues.
  • Quick Fixes & Refactoring: Rename, go-to definition, and format with ease.
  • Hover Info: Get detailed information about TypeSpec elements by hovering over them.

Create TypeSpec Project

Create TypeSpec Project: Easily initialize a new TypeSpec project based on a template, ensuring a structured and ready-to-use setup.

Emit Code from TypeSpec

The extension allows emitting various outputs from TypeSpec:

  • OpenAPI Specification
  • Server SDKs: Emit server stubs for different back-end frameworks.
  • Client SDKs: Emit client code for multiple languages, including:
    • .NET (C#)
    • Python
    • Java
    • JavaScript/TypeScript

The action appears in the context menu on a tsp file. Or invoke TypeSpec: Emit From TypeSpec to emit needed outputs:

Import TypeSpec from OpenAPI 3

The extension supports to import TypeSpec from OpenAPI 3. Invoke TypeSpec: Import TypeSpec from OpenAPI 3 to begin importing.

Preview API Documentation

The extension allows to preview API documentation. The action appears in the context menu on a tsp file. Or invoke TypeSpec: Preview API Documentation to visualize your API definitions.

Commands

The extension provides the following commands:

Command Description
TypeSpec: Create TypeSpec Project Scaffold a new TypeSpec project.
TypeSpec: Install TypeSpec Compiler/CLI globally Install the TypeSpec Compiler/CLI globally.
TypeSpec: Emit from TypeSpec Compile and generate from TypeSpec files into the specified output.
TypeSpec: Restart TypeSpec Server Restart the TypeSpec language server.
TypeSpec: Show Output Channel Open the TypeSpec output channel to view logs.
TypeSpec: Preview API Documentation Preview API documentation generated from TypeSpec in the workspace.
TypeSpec: Import TypeSpec from OpenAPI 3 Import TypeSpec from existing OpenAPI 3 definitions

Configuration

TypeSpec will interpolate a few variables using this pattern ${<name>}. For example ${workspaceFolder}.

Available variables:

  • workspaceFolder: Correspond to the root of your Visual Studio workspace.

`typespec.tsp-server.path`: Configure the server path

There are cases where the TypeSpec project is located in a subfolder. In such cases, the TypeSpec extension is not able to find the tsp compiler automatically and needs a little guidance.
This setting provides the ability to configure where the tsp compiler is located.

json
{
  "typespec.tsp-server.path": "${workspaceFolder}/my-nested-project/node_modules/@typespec/compiler"
}

Telemetry

The TypeSpec Extension for Visual Studio Code collects usage data and sends it to Microsoft to help improve our products and services. Read our privacy statement to learn more. This extension respects the telemetry.telemetryLevel setting which you can find more information in the FAQ and Telemetry Data.


Overview (`packages/versioning/README.md`)

@typespec/versioning

TypeSpec library for declaring and emitting versioned APIs

Install

bash
npm install @typespec/versioning

Usage

Consuming versioning library from an emitter

Get the service representation at a given version

Versioning library works with projection to project the service at a given version.

ts
// Get a list of all the different version of the service and the projections
const projections = buildVersionProjections(program, serviceNamespace);

for (const projection of projections) {
  const projectedProgram = projectProgram(program, projection.projections);
  // projectedProgram now contains the representation of the service at the given version.
}

Get list of versions and version dependency across namespaces

Versioning library works with projection to project the service at a given version.

ts
const versions = resolveVersions(program, serviceNamespace);
// versions now contain a list of all the version of the service namespace and what version should all the other dependencies namespace use.

Consume versioning manually

If the emitter needs to have the whole picture of the service evolution across the version then using the decorator accessor will provide the metadata for each type:

  • getAddedOn
  • getRemovedOn
  • getRenamedFromVersion
  • getMadeOptionalOn

Decorators

TypeSpec.Versioning

`@added`

Identifies when the target was added.

typespec
@TypeSpec.Versioning.added(version: EnumMember)

Target

Model | ModelProperty | Operation | Enum | EnumMember | Union | UnionVariant | Scalar | Interface

Parameters

Name Type Description
version EnumMember The version that the target was added in.

Examples

tsp
@added(Versions.v2)
op addedInV2(): void;

@added(Versions.v2)
model AlsoAddedInV2 {}

model Foo {
  name: string;

  @added(Versions.v3)
  addedInV3: string;
}

`@madeOptional`

Identifies when a target was made optional.

typespec
@TypeSpec.Versioning.madeOptional(version: EnumMember)

Target

ModelProperty

Parameters

Name Type Description
version EnumMember The version that the target was made optional in.

Examples

tsp
model Foo {
  name: string;

  @madeOptional(Versions.v2)
  nickname?: string;
}

`@madeRequired`

Identifies when a target was made required.

typespec
@TypeSpec.Versioning.madeRequired(version: EnumMember)

Target

ModelProperty

Parameters

Name Type Description
version EnumMember The version that the target was made required in.

Examples

tsp
model Foo {
  name: string;

  @madeRequired(Versions.v2)
  nickname: string;
}

`@removed`

Identifies when the target was removed.

typespec
@TypeSpec.Versioning.removed(version: EnumMember)

Target

Model | ModelProperty | Operation | Enum | EnumMember | Union | UnionVariant | Scalar | Interface

Parameters

Name Type Description
version EnumMember The version that the target was removed in.

Examples

tsp
@removed(Versions.v2)
op removedInV2(): void;

@removed(Versions.v2)
model AlsoRemovedInV2 {}

model Foo {
  name: string;

  @removed(Versions.v3)
  removedInV3: string;
}

`@renamedFrom`

Identifies when the target has been renamed.

typespec
@TypeSpec.Versioning.renamedFrom(version: EnumMember, oldName: valueof string)

Target

Model | ModelProperty | Operation | Enum | EnumMember | Union | UnionVariant | Scalar | Interface

Parameters

Name Type Description
version EnumMember The version that the target was renamed in.
oldName valueof string The previous name of the target.

Examples

tsp
@renamedFrom(Versions.v2, "oldName")
op newName(): void;

`@returnTypeChangedFrom`

Declares that the return type of an operation has changed starting at a given version,
while keeping earlier versions consistent with the previous return type.

This decorator is used to track return type changes across API versions. When applied,
the operation will return oldType in versions before the specified version,
and the current return type definition in the specified version and later.

typespec
@TypeSpec.Versioning.returnTypeChangedFrom(version: EnumMember, oldType: unknown)

Target

Operation

Parameters

Name Type Description
version EnumMember The version when the return type change takes effect. The new return type appliesfrom this version onwards, while the old return type applies to earlier versions.
oldType unknown The previous return type used before the specified version.

Examples

tsp
// In v1: returns a string
// In v2+: returns an int32
@returnTypeChangedFrom(Versions.v2, string)
op getUserId(): int32;

`@typeChangedFrom`

Declares that the type of a model property has changed starting at a given version,
while keeping earlier versions consistent with the previous type.

This decorator is used to track type changes across API versions. When applied,
the property will use oldType in versions before the specified version,
and the current type definition in the specified version and later.

typespec
@TypeSpec.Versioning.typeChangedFrom(version: EnumMember, oldType: unknown)

Target

ModelProperty

Parameters

Name Type Description
version EnumMember The version when the type change takes effect. The new type appliesfrom this version onwards, while the old type applies to earlier versions.
oldType unknown The previous type used before the specified version.

Examples

tsp
model Foo {
  // In v1: id is a string
  // In v2+: id is an int32
  @typeChangedFrom(Versions.v2, string)
  id: int32;
}

`@useDependency`

Identifies that a namespace or a given versioning enum member relies upon a versioned package.

typespec
@TypeSpec.Versioning.useDependency(...versionRecords: EnumMember[])

Target

EnumMember | Namespace

Parameters

Name Type Description
versionRecords EnumMember[] The dependent library version(s) for the target namespace or version.

Examples

Select a single version of `MyLib` to use

tsp
@useDependency(MyLib.Versions.v1_1)
namespace NonVersionedService;

Select which version of the library match to which version of the service.

tsp
@versioned(Versions)
namespace MyService1;
enum Version {
  @useDependency(MyLib.Versions.v1_1) // V1 use lib v1_1
  v1,
  @useDependency(MyLib.Versions.v1_1) // V2 use lib v1_1
  v2,
  @useDependency(MyLib.Versions.v2) // V3 use lib v2
  v3,
}

`@versioned`

Identifies that the decorated namespace is versioned by the provided enum.

typespec
@TypeSpec.Versioning.versioned(versions: Enum)

Target

Namespace

Parameters

Name Type Description
versions Enum The enum that describes the supported versions.

Examples

tsp
@versioned(Versions)
namespace MyService;
enum Versions {
  v1,
  v2,
  v3,
}

Overview (`packages/xml/README.md`)

@typespec/xml

TypeSpec library providing xml bindings

Install

bash
npm install @typespec/xml

Decorators

TypeSpec.Xml

`@attribute`

Specify that the target property should be encoded as an XML attribute instead of node.

typespec
@TypeSpec.Xml.attribute

Target

ModelProperty

Parameters

None

Examples

Default

tsp
model Blob {
  id: string;
}
xml
<Blob>
  <id>abcdef</id>
</Blob>

With `@attribute`

tsp
model Blob {
  @attribute id: string;
}
xml
<Blob id="abcdef">
</Blob>

`@name`

Provide the name of the XML element or attribute. This means the same thing as
@encodedName("application/xml", value)

typespec
@TypeSpec.Xml.name(name: valueof string)

Target

unknown

Parameters

Name Type Description
name valueof string The name of the XML element or attribute

Examples

tsp
@name("XmlBook")
model Book {
  @name("XmlId") id: string;
  @encodedName("application/xml", "XmlName") name: string;
  content: string;
}
xml
<XmlBook>
  <XmlId>string</XmlId>
  <XmlName>string</XmlName>
  <content>string</content>
</XmlBook>

`@ns`

Specify the XML namespace for this element. It can be used in 2 different ways:

  1. @ns("http://www.example.com/namespace", "ns1") - specify both namespace and prefix
  2. @Xml.ns(Namespaces.ns1) - pass a member of an enum decorated with @nsDeclaration
typespec
@TypeSpec.Xml.ns(ns: string | EnumMember, prefix?: valueof string)

Target

unknown

Parameters

Name Type Description
ns string | EnumMember The namespace URI or a member of an enum decorated with @nsDeclaration.
prefix valueof string The namespace prefix. Required if the namespace parameter was passed as a string.

Examples

With strings

tsp
@ns("https://example.com/ns1", "ns1")
model Foo {
  @ns("https://example.com/ns1", "ns1")
  bar: string;

  @ns("https://example.com/ns2", "ns2")
  bar: string;
}

With enum

tsp
@Xml.nsDeclarations
enum Namespaces {
  ns1: "https://example.com/ns1",
  ns2: "https://example.com/ns2",
}

@Xml.ns(Namespaces.ns1)
model Foo {
  @Xml.ns(Namespaces.ns1)
  bar: string;

  @Xml.ns(Namespaces.ns2)
  bar: string;
}

`@nsDeclarations`

Mark an enum as declaring XML namespaces. See @ns

typespec
@TypeSpec.Xml.nsDeclarations

Target

Enum

Parameters

None

`@unwrapped`

Specify that the target property shouldn't create a wrapper node. This can be used to flatten list nodes into the model node or to include raw text in the model node.
It cannot be used with @attribute.

typespec
@TypeSpec.Xml.unwrapped

Target

ModelProperty

Parameters

None

Examples

Array property default

tsp
model Pet {
  tags: Tag[];
}
xml
<XmlPet>
  <ItemsTags>
    <XmlTag>
      <name>string</name>
    </XmlTag>
  </ItemsTags>
</XmlPet>

Array property with `@unwrapped`

tsp
model Pet {
  @unwrapped tags: Tag[];
}
xml
<XmlPet>
  <XmlTag>
    <name>string</name>
  </XmlTag>
</XmlPet>

String property default

tsp
model BlobName {
  content: string;
}
xml
<BlobName>
  <content>
    abcdef
  </content>
</BlobName>

Array property with `@unwrapped`

tsp
model BlobName {
  @unwrapped content: string;
}
xml
<BlobName>
  abcdef
</BlobName>

Overview (`packages/http-client-csharp/generator/README.md`)

Microsoft TypeSpec Generator

The Microsoft TypeSpec Generator is a code generation library that is bundled with the @typespec/http-client-csharp emitter to transform TypeSpec API definitions into strongly-typed C# client libraries for accessing RESTful web services. The generator provides a modular, extensible architecture that supports multiple output formats and customization scenarios.

Table of Contents

Architecture Overview

See the architecture documentation here.

Prerequisites

Build

  1. dotnet build (at root)

Test

  1. dotnet test (at root)

Overview (`packages/http-client-java/emitter/README.md`)

Java emitter code model

The TypeScript emitter converts the TCGC SDK model into the data contract consumed
by the Java generator. The interfaces, factories, and mutation helpers in
src/common/ are owned by this emitter; they do not require the AutoRest codemodel
or codegen npm packages.

Model nodes are plain objects. Schema types use their existing type field for
narrowing, and response/parameter helpers use existing wire fields rather than
adding private discriminators. common/schemas.ts registers schemas in the
collections expected by Java and preserves the existing deduplication rules.

Keep object identity intact when modifying this code: recursive schemas, parent
clients, parameters, and properties can share references or form cycles. Factories
must also preserve metadata defaults, omitted fields, and container-copy behavior.
Do not replace these references with JSON clones.

The interchange format remains code-model.yaml, serialized as YAML 1.1 for the
Java backend's SnakeYAML reader. This is still the AutoRest-derived code-model
contract; removing the npm dependencies does not change the Java backend or the
external AutoRest flow.

test/code-model.test.ts covers model initialization, registration, reference
identity, signature filtering, plain-object graphs, and TypeSpec-to-YAML snapshots
captured before the dependency removal, including LRO, subclient, multipart, and
XML-bytes scenarios. Snapshot keys are sorted without cloning away aliases, so
mapping order does not obscure contract changes.


Overview (`packages/http-client-java/generator/README.md`)

Microsoft Java client generator

The Microsoft Java client generator tool generates client libraries for accessing RESTful web services.

Table of Contents

Prerequisites

Build

  1. mvn clean compile (from packages/http-client-java/generator directory)

Test

  1. mvn clean test (from packages/http-client-java/generator directory)

Debug

Debugging Java Code

Run Main class

  • Add --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED to VM options.

Overview (`packages/samples/specs/grpc-kiosk-example/README.md`)

Introduction

This example demonstrates the use of Protocol Buffers to define and implement a networked API. The Kiosk API allows users to manage a collection of displays that are able to display digital signs containing text and images. The Kiosk API includes a streaming method that allows displays to immediately update in response to configuration changes on the API server. The Kiosk API is implemented using gRPC, which includes support for streaming APIs. All non-streaming API methods can also be provided as REST services using standard gRPC to JSON transcoding.

The original gRPC-based API definition is in the googleapis/kiosk repository.

We have taken the gRPC representation of this service and converted it into TypeSpec to understand what gaps might be in the language. Note we're not trying to create an TypeSpec which could create the equivalent protobuf as an output format, but are focusing on the swagger created from both gRPC and TypeSpec and trying to understand the differences.

Files

  • kiosk.proto - The original kiosk.proto.
  • kiosk.swagger.json - Swagger generated from the kiosk.proto file using protoc-gen-openapiv2.
  • kiosk.tsp - A hand-written TypeSpec file which tries to stay as close as possible to a RPC based request/response model.
  • types.tsp - Some generic types which could move into a standard type library.
  • openapi.json - Swagger generated by tsp compile from kiosk.tsp.

Open questions and comments

`rpc CreateKiosk(Kiosk)` is broken in OpenAPI

The gRPC definition of CreateKiosk:

protobuf
// Create a kiosk. This enrolls the kiosk for sign display.
rpc CreateKiosk(Kiosk) returns (Kiosk) {
    option (google.api.http) = { post: "/v1/kiosks" };
}

doesn't translate at all to OpenAPI and the Swagger produced has no request body:

json
"post": {
   "summary": "Create a kiosk. This enrolls the kiosk for sign display.",
   "operationId": "Display_CreateKiosk",
   "responses": {
       "200": {
       "description": "A successful response.",
       "schema": {
           "$ref": "#/definitions/kioskKiosk"
       }
       },
       "default": {
       "description": "An unexpected error response.",
       "schema": {
           "$ref": "#/definitions/rpcStatus"
       }
       }
   },
   "tags": [
       "Display"
   ]
   }
}

TypeSpec has an advantage as it won't even allow the corresponding op to compile and it will throw an error that there isn't a unique body specified. Alteratively, the library service does it better and in a way that translates cleanly to TypeSpec:

protobuf
// Request message for LibraryService.CreateBook.
message CreateBookRequest {
    // The name of the shelf in which the book is created.
    string name = 1 [
        (google.api.field_behavior) = REQUIRED,
        (google.api.resource_reference).type = "Shelf"
    ];

    // The book to create.
    Book book = 2 [(google.api.field_behavior) = REQUIRED];
}

// Creates a book, and returns the new Book.
rpc CreateBook(CreateBookRequest) returns (Book) {
    option (google.api.http) = {
      post: "/v1/{name=shelves/*}/books"
      body: "book"
    };
    option (google.api.method_signature) = "name,book";
}

Using `...` in Request parameters

In this example and the Library example we see a few different patterns on how a gRPC rpc definition uses different message. While some directly use things that look like TypeSpec models as request and response messages, a more general patter is that a gRPC op Foo usually has FooRequest and FooResponse messages which embed models inside, e.g.

a natural direct mapping of:

protobuf
message GetKioskRequest {
  // Required.
  int32 id = 1;
}

// Get a kiosk.
rpc GetKiosk(GetKioskRequest) returns (Kiosk) {
    option (google.api.http) = { get: "/v1/kiosks/{id}" };
}

would be

text
model GetKioskRequest {
    @path
    id: int32;
}

@doc("Get a kiosk.")
@get
@route("kiosks/{id}")
op GetKiosk(... GetKioskRequest): Kiosk | RpcStatus;

This produces slightly different, but functionality equivalent swagger in that the gRPC generator inline the parameter id, while TypeSpec create a "$ref": "#/parameters/GetKioskRequest" which points to a single parameter, id.

If we wrote the TypeSpec directly with the id specified in the op parameters we get equivalent to the gRPC version:

text
@doc("Get a kiosk.")
@get
@route("kiosks/{id}")
op GetKiosk(@path id: int32): Kiosk | RpcStatus;

the downside with this approach is that it's not as convenient to add documentation inside the parameter. Could we perhaps add a new decorator @parameter_doc("id","This is a unique identified") that we add to the op ?


Overview (`packages/samples/specs/grpc-library-example/README.md`)

Introduction

This is a Google example service representing a simple digital library.
It manages a collection of shelf resources, and each shelf owns a collection
of book resources.

The original gRPC-based API definition is in the googleapis/googleapis repository.

It follows the Google API Design Guidelines. We have taken the gRPC representation of this service and converted it into TypeSpec to understand what gaps might be in the language. Note we're not trying to create an TypeSpec which could create the equivalent protobuf as an output format, but are focusing on the swagger created from both gRPC and TypeSpec and trying to understand the differences.

Files

Open Questions

How to handle a empty response

for an op of form op Foo(): null we currently emit the responses block as follows:

json
"responses": {
    "200": {
        "description": "Null response"
    },

whereas when generated from gRPC definition it is:

json
"responses": {
    "200": {
        "description": "A successful response.",
        "schema": {
            "properties": {}
        }
    },

GRPC path mapping

Google uses structured names for resource names. e.g.

protobuf
// A Shelf contains a collection of books with a theme.
message Shelf {
  option (google.api.resource) = {
    type: "library-example.googleapis.com/Shelf",
    pattern: "shelves/{shelf_id}"
  };
  // The resource name of the shelf.
  // Shelf names have the form `shelves/{shelf_id}`.
  // The name is ignored when creating a shelf.
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference).type = "library-example.googleapis.com/Shelf"
  ];
}

Since the name includes part of the standard REST path, gRPC allows a more complex path mapping for the gRPC reverse proxy - (ref: gRPC transcoding). In particular it allows for constructs like:

text
// Gets a shelf. Returns NOT_FOUND if the shelf does not exist.
  rpc GetShelf(GetShelfRequest) returns (Shelf) {
    option (google.api.http) = {
      get: "/v1/{name=shelves/*}"
    };
    option (google.api.method_signature) = "name";
  }

This means the URI for a GET on a shelf with name shelves/fiction is /v1/shelves/fiction. The grpc-openapiv2 generator doesn't handle this path mapping so you actually get invalid paths in the swagger. See the Kiosk gRPC example for a gRPC examples with OpenAPI compatible path definitions.


Overview (`packages/samples/specs/local-typespec/README.md`)

Installing TypeSpec to project local path

This sample shows how to install TypeSpec locally to a project and have the VS Code
extension use its locally installed language server.

See typespec.tsp-server.path in .vscode/settings.json and
@typespec/compiler dev dependency in package.json.

Steps

1. Install TypeSpec to project:

text
npm install --save-dev @typespec/compiler

2. Configure path in VS Code

  • File -> Preferences

  • Search for TypeSpec

  • Click on Workspace tab

  • Set typespec.tsp-server.path to ${workspaceFolder}/node_modules/@typespec/compiler

    (NOTE: If the TypeSpec project is not at the root of the workspace, adjust the path accordingly.
    For example, ${workspaceFolder}/path/from/workspace/to/project/node_modules/@typespec/compiler)

3. Commit settings.json and package.json changes


Overview (`docs/samples/client/csharp/SampleService/readme.md`)

Sample Service

This package represents a sample HTTP service and the generated code produced after compiling with the @typespec/http-client-csharp emitter. The package makes use of a logging plugin in order to enrich each method with logs indicating when the method is called and any exceptions thrown from the method. The logging-plugin is specified in the package.json file. In order to compile this project locally, clone the repo, navigate to the SampleService directory and run the following command:
tsp compile .


Overview (`docs/samples/client/csharp/oauth/readme.md`)

OAuth 2.0 Credential Support

This guide provides example implementations of OAuth 2.0 credentials to enable secure authentication in clients generated by TypeSpec.
With this approach, you can define OAuth 2.0 authentication directly in your TypeSpec files and supply custom credential implementations for a smooth, end-to-end authentication flow.

For more details, refer to the TypeSpec documentation: Oauth2Auth

Getting Started

Create custom client constructor to support OAuth token

This constructor initializes a new instance of the SampleTypeSpecClient class and sets a client pipeline with OAuth2 bearer token authentication using the provided AuthenticationTokenProvider and endpoint URI.

csharp
using System.ClientModel;
using System.ClientModel.Primitives;

/// <summary>
/// Customize client to add custom constructor.
/// </summary>
public partial class SampleTypeSpecClient
{
  public SampleTypeSpecClient(Uri uri, AuthenticationTokenProvider tokenProvider)
  {
      _endpoint = uri;
      var options = new ClientPipelineOptions();
      Pipeline = ClientPipeline.Create(
          options,
          perCallPolicies: ReadOnlySpan<PipelinePolicy>.Empty,
          perTryPolicies: [new OAuth2BearerTokenAuthenticationPolicy(tokenProvider, flows)],
          beforeTransportPolicies: ReadOnlySpan<PipelinePolicy>.Empty
      );
  }
}

The full code is available here.

Example implementation of AuthenticationTokenProvider

Below is an example implementation of the AuthenticationTokenProvider class that supports the client credentials flow.

text
/* Detailed source-code truncated for AI context efficiency. */

Example usage of AuthenticationTokenProvider and Custom Client

This example demonstrates how to use the AuthenticationTokenProvider with the custom client constructor to authenticate and make a request to the API.

csharp
AuthenticationTokenProvider provider = new ClientCredentialTokenProvider("myClientId", "myClientSecret");
var client = new SampleTypeSpecClient(new Uri("http://localhost:5000"), provider);
client.Widget("myId");

Overview (`docs/samples/client/java/oauth/README.md`)

OAuth 2.0 Credential Support

This guide provides example implementations of OAuth 2.0 credentials to enable secure authentication in clients generated by TypeSpec.
With this approach, you can define OAuth 2.0 authentication directly in your TypeSpec files and supply custom credential implementations for a smooth, end-to-end authentication flow.

For more details, refer to the TypeSpec documentation: Oauth2Auth

Generate a client with OAuth2Auth

In your TypeSpec, create a client using the OAuth2Auth flow:

typespec
import "@typespec/http";

using TypeSpec.Http;

@useAuth(
  OAuth2Auth<[
    {
      type: OAuth2FlowType.clientCredentials,
      tokenUrl: "https://tokenUrl",
      scopes: ["scope"],
    }
  ]>
)
@service(#{ title: "Example Server API" })
@server("https://endpoint", "Example Server Endpoint")
namespace ExampleServer {
  model Result {
    value: string;
  }

  // Test API endpoint
  @route("/api/getValue")
  @get
  op getResult(): Result[];
}

Example implementation of OAuthTokenCredential

Below is an example implementation of the OAuthTokenCredential class that supports the client credentials flow. This sample should be modified to meet your needs, including:

  • Choosing the appropriate OAuth flow.
  • Choosing the appropriate JSON library for your project. Jackson is used here only as an example.
  • Appropriate error handling and logging.
text
/* Detailed source-code truncated for AI context efficiency. */

Example usage of OAuthTokenCredential and Custom Client

This example demonstrates how to use the AuthenticationTokenProvider with the custom client constructor to authenticate and make a request to the API.

java
TestCredential credential = new TestCredential().setClientId("myClientId").setClientSecret("myClientSecret");
var client = new ExampleServerClientBuilder().credential(credential).build();
client.getResult();

Overview (`docs/samples/client/js/oauth/readme.md`)

Authenticating with OAuth2TokenCredential

This readme demonstrates how to implement OAuth2TokenCredential using popular third-party authentication libraries and integrate it with the generated TypeSpec client. It provides examples for both Authorization Code Flow using @auth0/auth0-spa-js and Client Credentials Flow using auth0.

Table of contents

Create OAuth2 Credential

Authorization Code Flow with `@auth0/auth0-spa-js`

The following sample illustrates using @auth0/auth0-spa-js to acquire the access token for authorization code flow. The credential created is passed into the constructor of the SampleTypeSpecClient and the client will set the appropriate authentication policy using the information provided. The method getOAuth2Token will be called to get the token to authenticate the request.

text
/* Detailed source-code truncated for AI context efficiency. */

Client Credential Flow with `auth0`

The following sample illustrates using auth0 to acquire the access token for authorization code flow. The credential created is passed into the constructor of the SampleTypeSpecClient and the client will set the appropriate authentication policy using the information provided. The method getOAuth2Token will be called to get the token to authenticate the request.

ts
import { OAuth2TokenCredential, ClientCredentialsFlow } from "@typespec/ts-http-runtime";
import { AuthenticationClient } from "auth0";
import { SampleTypeSpecClient } from "SampleTypeSpecSDK";

// Create an OAuth2 credential that implements authorization code flow
const credential: OAuth2TokenCredential<ClientCredentialsFlow> = {
  async getOAuth2Token(flows: ClientCredentialsFlow[]) {
    const { tokenUrl } = flows[0];
    const option = {
      domain: tokenUrl,
      clientId: "SampleClientID",
      clientSecret: "SampleClientSecret",
    };
    const client = new AuthenticationClient(option);
    const response = await client.oauth.clientCredentialsGrant({ audience: "SampleAudience" });
    return response.data.access_token;
  },
};

// Authentication shemes that the services allow
const authorizationCodeScheme: AuthScheme = {
  kind: "oauth2",
  flows: [
    {
      kind: "clientCredentials",
      tokenUrl: "https://example.com/token",
      scopes: ["sampleScope1", "sampleScope2"],
    },
  ],
};

// Pass the credential to the client and use the client to make a request to the service
const client = new SampleTypeSpecClient(credential, {
  authSchemes: [authorizationCodeScheme],
});

Overview (`docs/samples/client/python/oauth/README.md`)

OAuth 2.0 Credential Support

This guide provides example implementations of OAuth 2.0 credentials to enable secure authentication in clients generated by TypeSpec. With this approach, you can define OAuth 2.0 authentication directly in your TypeSpec files and supply custom credential implementations for a smooth, end-to-end authentication flow.

For more details, refer to the TypeSpec documentation: Oauth2Auth

Getting Started

Define OAuth 2.0 in TypeSpec

In the .tsp file, declare the use of OAuth 2.0:

python
@useAuth(OAuth2Auth<[
    {
      type: OAuth2FlowType.clientCredentials,
      authorizationUrl: "https://auth.example.com/authorize",
      tokenUrl: "https://auth.example.com/token",
      scopes: ["read", "write"],
    }
  ]>
)

Implement the OAuth2 Credential

Follow the sample implementation to create a credential class that includes the get_token_info method, which returns an AccessTokenInfo object. This follows the credential interface defined in the corehttp package.

Use the Credential with the Generated Client

py
credential = MyOAuth2Credential()
client = MyGeneratedClient(endpoint, credential)

result = client.my_operation()
print(result)

Overview (`packages/http-server-csharp/test/snapshots/sample-service/README.md`)


title: Next Steps

Next Steps

  • Use the SwaggerUI endpoint to test out the running service
  • Update business logic mocks with real business logic
    • mocks/Pets.cs
    • mocks/Dogs.cs
  • Update mocks/MockRegistration.cs if you update the constructor of business logic implementations.
  • Update development and production configuration to suit your needs

More Information


Packages Http Client Csharp Docs Index (`packages/http-client-csharp/docs/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from "@astrojs/starlight/components";

TypeSpec library for emitting Http Client libraries for C#.

Install

bash
npm install @typespec/http-client-csharp
bash
npm install --save-peer @typespec/http-client-csharp

Emitter usage

See documentation

Generator plugins

See documentation

TypeSpec.HttpClient

TypeSpec.HttpClient.CSharp

Decorators


Packages Http Server Csharp Docs Index (`packages/http-server-csharp/docs/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from "@astrojs/starlight/components";

TypeSpec service code generator for c-sharp

Install

bash
npm install @typespec/http-server-csharp
bash
npm install --save-peer @typespec/http-server-csharp

Emitter usage

See documentation
Overview of Generated Code


Website Src Content Docs Docs Getting Started Getting Started (`website/src/content/docs/docs/getting-started/getting-started.md`)


id: getting-started title: Getting Started


Website Src Content Docs Docs Language Basics Overview (`website/src/content/docs/docs/language-basics/overview.md`)


id: overview title: Overview llmstxt: true

This document provides a concise overview of the language concepts in TypeSpec. It serves as a quick reference guide rather than an in-depth tutorial.

Declarations

  • Names of declarations must be unique across different types within the same scope. For instance, the following is not permissible:

    typespec
    model Dog {}
    namespace Dog {}

Imports

For more details, see: Imports

Feature Example
Import TypeSpec file import "./models.tsp"
Import JS file import "./models.js"
Import Library import "@typespec/rest"

Namespaces

For more details, see: Namespaces

Feature Example
Declare namespace namespace PetStore {}
File namespace namespace PetStore;
Nested namespace namespace PetStore.Models;
Using namespace using PetStore.Models;

Decorators

For more details, see: Decorators

Feature Example
Use decorator @mark
Use decorator with arguments @tag("abc")
Declare a decorator in JS export function $tag(context: DecoratorContext, target: Type, name: string) {...}
Save state in decorator context.program.stateMap(key).set(target, <value>)
Augment decorator @@tag(MyType, "abc");

Scalars

For more details, see: Scalars

Feature Example
Scalar declaration scalar ternary
Extend scalar scalar Password extends string
Template scalar @doc(T) scalar Password<T extends string>

Models

For more details, see: Models

Feature Example
Model declaration model Pet {}
Model inheritance model Dog extends Pet {}
scalar is model uuid extends string;
Model spread model Dog {...Animal}
Property model Dog { name: string }
Optional property model Dog { owner?: string }
Optional property with default model Dog { name?: string = "Rex" }
Model template model Pet<T> { t: T }

Operations

For more details, see: Operations

Feature Example
Operation declaration op ping(): void
Operation with parameters op upload(filename: string, data: bytes): void
Operation with return type op health(): HealthStatus
Operation with multiple types op health(): HealthStatus | ErrorResponse
Operation template op getter<T>(id: string): T
Operation is op getPet is getter;

Interfaces

For more details, see: Interfaces

Feature Example
Interface declaration interface PetStore { list(): Pet[] }
Interface composition interface PetStore extends Store { }
Interface template interface Restful<T> { list(): T[] }

Templates

For more details, see: Templates

Feature Example
Simple template model Response<T> {value: T}
Template with multiple parameters model Response<K, V> {key: K, value: T}
Template default model Response<T = string> {value: T}
Template constraints model Response<T extends {id: string}> {value: T}
Template constraints and defaults model Response<T extends string = ""> {value: T}

Enums

For more details, see: Enums

Feature Example
Enum declaration enum Direction {Up, Down}
Enum string values enum Direction {Up: "up", Down: "down"}
Enum int values enum Size {Small: 1000, Large: 2000}
Enum float values enum Part {Quarter: 0.25, Half: 0.5}
Enum composing enum Direction2D {...Direction, Left, Right}

Unions

For more details, see: Unions

Feature Example
Union declaration "cat" | "dog"
Named union declaration union Pet {cat: Cat, dog: Dog}

Intersections

For more details, see: Intersections

Feature Example
Intersection declaration Pet & Animal

Type literals

For more details, see: Type literals

Feature Example
String "Hello world!"
Multi line String """\nHello world!\n""" (\n) represent actual new lines
Int 10
Float 10.0
Boolean false

Aliases

For more details, see: Aliases

Feature Example
Alias declaration alias Options = "one" | "two";

Website Src Content Docs Docs Emitters Graphql Reference Index (`website/src/content/docs/docs/emitters/graphql/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library for emitting GraphQL

Install

bash
npm install @typespec/graphql
bash
npm install --save-peer @typespec/graphql

Emitter usage

See documentation

TypeSpec.GraphQL

Decorators

TypeSpec.GraphQL.Schema

Models


Website Src Content Docs Docs Emitters Json Schema Reference Index (`website/src/content/docs/docs/emitters/json-schema/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library for emitting TypeSpec to JSON Schema and converting JSON Schema to TypeSpec

Install

bash
npm install @typespec/json-schema
bash
npm install --save-peer @typespec/json-schema

Emitter usage

See documentation

TypeSpec.JsonSchema

Decorators

Models


Website Src Content Docs Docs Emitters Openapi3 Reference Index (`website/src/content/docs/docs/emitters/openapi3/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library for emitting OpenAPI 3.0 and OpenAPI 3.1 from the TypeSpec REST protocol binding and converting OpenAPI3 to TypeSpec

Install

bash
npm install @typespec/openapi3
bash
npm install --save-peer @typespec/openapi3

Emitter usage

See documentation

TypeSpec.OpenAPI

Decorators


Website Src Content Docs Docs Emitters Protobuf Reference Index (`website/src/content/docs/docs/emitters/protobuf/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library and emitter for Protobuf (gRPC)

Install

bash
npm install @typespec/protobuf
bash
npm install --save-peer @typespec/protobuf

Emitter usage

See documentation

TypeSpec.Protobuf

Decorators

Models

TypeSpec.Protobuf.WellKnown

Models


Website Src Content Docs Docs Libraries Events Reference Index (`website/src/content/docs/docs/libraries/events/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library providing events bindings

Install

bash
npm install @typespec/events
bash
npm install --save-peer @typespec/events

TypeSpec.Events

Decorators


Website Src Content Docs Docs Libraries Http Server Js Reference Index (`website/src/content/docs/docs/libraries/http-server-js/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec HTTP server code generator for JavaScript

Install

bash
npm install @typespec/http-server-js
bash
npm install --save-peer @typespec/http-server-js

Emitter usage

See documentation


Website Src Content Docs Docs Libraries Http Reference Index (`website/src/content/docs/docs/libraries/http/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec HTTP protocol binding

Install

bash
npm install @typespec/http
bash
npm install --save-peer @typespec/http

TypeSpec.Http

Decorators

Models


Website Src Content Docs Docs Libraries Openapi Reference Index (`website/src/content/docs/docs/libraries/openapi/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library providing OpenAPI concepts

Install

bash
npm install @typespec/openapi
bash
npm install --save-peer @typespec/openapi

TypeSpec.OpenAPI

Decorators

Models


Website Src Content Docs Docs Libraries Rest Reference Index (`website/src/content/docs/docs/libraries/rest/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec REST protocol binding

Install

bash
npm install @typespec/rest
bash
npm install --save-peer @typespec/rest

TypeSpec.Rest

Decorators

TypeSpec.Rest.Resource

Interfaces

Models


Website Src Content Docs Docs Libraries Sse Reference Index (`website/src/content/docs/docs/libraries/sse/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library providing server sent events bindings

Install

bash
npm install @typespec/sse
bash
npm install --save-peer @typespec/sse

TypeSpec.SSE

Decorators

Models


Website Src Content Docs Docs Libraries Streams Reference Index (`website/src/content/docs/docs/libraries/streams/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library providing stream bindings

Install

bash
npm install @typespec/streams
bash
npm install --save-peer @typespec/streams

TypeSpec.Streams

Decorators

Models


Website Src Content Docs Docs Libraries Versioning Reference Index (`website/src/content/docs/docs/libraries/versioning/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library for declaring and emitting versioned APIs

Install

bash
npm install @typespec/versioning
bash
npm install --save-peer @typespec/versioning

TypeSpec.Versioning

Decorators


Website Src Content Docs Docs Libraries Xml Reference Index (`website/src/content/docs/docs/libraries/xml/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library providing xml bindings

Install

bash
npm install @typespec/xml
bash
npm install --save-peer @typespec/xml

TypeSpec.Xml

Decorators


Website Src Content Docs Docs Emitters Clients Http Client Csharp Reference Index (`website/src/content/docs/docs/emitters/clients/http-client-csharp/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from "@astrojs/starlight/components";

TypeSpec library for emitting Http Client libraries for C#.

Install

bash
npm install @typespec/http-client-csharp
bash
npm install --save-peer @typespec/http-client-csharp

Emitter usage

See documentation

TypeSpec.HttpClient.CSharp

Decorators


Website Src Content Docs Docs Emitters Clients Http Client Java Reference Index (`website/src/content/docs/docs/emitters/clients/http-client-java/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library for emitting Java client from the TypeSpec REST protocol binding

Install

bash
npm install @typespec/http-client-java
bash
npm install --save-peer @typespec/http-client-java

Emitter usage

See documentation


Website Src Content Docs Docs Emitters Clients Http Client Js Reference Index (`website/src/content/docs/docs/emitters/clients/http-client-js/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec library for emitting Http Client libraries for JavaScript/TypeScript

Install

bash
npm install @typespec/http-client-js
bash
npm install --save-peer @typespec/http-client-js

Emitter usage

See documentation


Website Src Content Docs Docs Emitters Clients Http Client Python Reference Index (`website/src/content/docs/docs/emitters/clients/http-client-python/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec emitter for Python SDKs

Install

bash
npm install @typespec/http-client-python
bash
npm install --save-peer @typespec/http-client-python

Emitter usage

See documentation


Website Src Content Docs Docs Emitters Servers Http Server Csharp Reference Index (`website/src/content/docs/docs/emitters/servers/http-server-csharp/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec service code generator for c-sharp

Install

bash
npm install @typespec/http-server-csharp
bash
npm install --save-peer @typespec/http-server-csharp

Emitter usage

See documentation


Website Src Content Docs Docs Emitters Servers Http Server Js Reference Index (`website/src/content/docs/docs/emitters/servers/http-server-js/reference/index.mdx`)


title: Overview sidebar_position: 0 toc_min_heading_level: 2 toc_max_heading_level: 3

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeSpec HTTP server code generator for JavaScript

Install

bash
npm install @typespec/http-server-js
bash
npm install --save-peer @typespec/http-server-js

Emitter usage

See documentation


Packages Http Client Csharp Docs Decorators (`packages/http-client-csharp/docs/decorators.md`)


title: "Decorators" toc_min_heading_level: 2 toc_max_heading_level: 3

TypeSpec.HttpClient.CSharp

`@dynamicModel` {#@TypeSpec.HttpClient.CSharp.dynamicModel}

Marks a model or namespace as dynamic, indicating it should generate dynamic model code.
Can be applied to Model or Namespace types.

typespec
@TypeSpec.HttpClient.CSharp.dynamicModel

Target

Model | Namespace

Parameters

None

Examples

tsp
@dynamicModel
model Pet {
  name: string;
  kind: string;
}

@dynamicModel
namespace PetStore {
  model Dog extends Pet {
    breed: string;
  }
}

Packages Http Client Csharp Docs Emitter (`packages/http-client-csharp/docs/emitter.md`)


title: "Emitter usage"

Emitter usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-client-csharp
  1. Via the config
yaml
emit:
  - "@typespec/http-client-csharp"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-client-csharp"
options:
  "@typespec/http-client-csharp":
    option: value

Emitter options

`emitter-output-dir`

Type: absolutePath

Defines the emitter output directory. Defaults to {output-dir}/@typespec/http-client-csharp
See Configuring output directory for more info

`api-version`

Type: string

For TypeSpec files using the @versioned decorator, set this option to the version that should be used to generate against.

`generate-protocol-methods`

Type: boolean

Set to false to skip generation of protocol methods. The default value is true.

`generate-convenience-methods`

Type: boolean

Set to false to skip generation of convenience methods. The default value is true.

`unreferenced-types-handling`

Type: "removeOrInternalize" | "internalize" | "keepAll"

Defines the strategy on how to handle unreferenced types. The default value is removeOrInternalize.

`new-project`

Type: boolean

Set to true to overwrite the csproj if it already exists. The default value is false.

`save-inputs`

Type: boolean

Set to true to save the tspCodeModel.json and Configuration.json files that are emitted and used as inputs to the generator. The default value is false.

`package-name`

Type: string

Define the package name. If not specified, the first namespace defined in the TypeSpec is used as the package name.

`debug`

Type: boolean

Set to true to automatically attempt to attach to a debugger when executing the C# generator. The default value is false.

`logLevel`

Type: "info" | "debug" | "verbose"

Set the log level for which to collect traces. The default value is info.

`disable-xml-docs`

Type: boolean

Set to true to disable XML documentation generation. The default value is false.

`disable-roslyn-reduce`

Type: boolean

Set to true to skip the Roslyn reduce (simplification) post-processing step. This speeds up generation and is useful when iterating quickly. The default value is false.

`generator-name`

Type: string

The name of the generator. By default this is set to ScmCodeModelGenerator. Generator authors can set this to the name of a generator that inherits from ScmCodeModelGenerator.

`emitter-extension-path`

Type: string

Allows emitter authors to specify the path to a custom emitter package, allowing you to extend the emitter behavior. This should be set to import.meta.url if you are using a custom emitter.

`plugins`

Type: array

Paths to generator plugin assemblies (DLLs) or directories containing plugin assemblies. Each plugin must contain a class that extends GeneratorPlugin. Paths may be absolute or relative to the resolved emitter-output-dir. For example, to load plugins that live in a codegen folder under the output directory:

yaml
options:
  "@typespec/http-client-csharp":
    plugins:
      - "codegen/MyPlugin.dll" # file relative to emitter-output-dir
      - "codegen" # directory containing plugin assemblies
      - "/abs/path/to/MyPlugin.dll" # absolute path used as-is

See Generator plugins for a walkthrough on authoring and using plugins.

`license`

Type: object

License information for the generated client code.

`sdk-context-options`

Type: object

The SDK context options that implement the CreateSdkContextOptions interface from the @azure-tools/typespec-client-generator-core package to be used by the CSharp emitter.


Packages Http Client Csharp Docs Plugins (`packages/http-client-csharp/docs/plugins.md`)


title: "Generator plugins"

Generator plugins

Plugins are a lightweight way to customize the C# generator without authoring a full custom generator. A plugin can register additional visitors, rewriters, metadata references, or shared source directories that participate in the normal generation pipeline. This is useful when you want to tweak the generated output (for example, rename types, add attributes, or post-process models) while still using the built-in ScmCodeModelGenerator.

The generator discovers plugins in two ways:

  1. Automatically, from the dist folder of any package listed in your project's node_modules.
  2. Explicitly, from the paths supplied via the plugins emitter option.

This document focuses on the plugins option, which lets emitter authors point the generator at one or more plugin assemblies (or directories/projects containing them).

Authoring a plugin

A plugin is a C# class that extends GeneratorPlugin and overrides Apply. The base class is exported via MEF using [InheritedExport], so any subclass is discovered automatically once its assembly is loaded — you do not need to add an [Export] attribute yourself.

csharp
using Microsoft.TypeSpec.Generator;

public class MyPlugin : GeneratorPlugin
{
    public override void Apply(CodeModelGenerator generator)
    {
        // Register a custom visitor that transforms the output library.
        generator.AddVisitor(new MyLibraryVisitor());
    }
}

The CodeModelGenerator passed to Apply exposes the extension points a plugin can use, including:

  • AddVisitor(LibraryVisitor visitor) — add a visitor that traverses and modifies the output library.
  • AddRewriter(LibraryRewriter rewriter) — add a rewriter to transform generated members.
  • AddMetadataReference(MetadataReference reference) — make additional assemblies available to the generated code.
  • AddSharedSourceDirectory(string sharedSourceDirectory) — include a directory of shared source files.

A visitor lets you hook into specific parts of the output. For example, to transform every model:

csharp
using Microsoft.TypeSpec.Generator;
using Microsoft.TypeSpec.Generator.Providers;

public class MyLibraryVisitor : LibraryVisitor
{
    protected override TypeProvider? VisitType(TypeProvider type)
    {
        // Inspect or modify the generated type here.
        return base.VisitType(type);
    }
}

Plugin project file

Build your plugin as a class library that references the generator's published NuGet package. Referencing Microsoft.TypeSpec.Generator.ClientModel transitively brings in Microsoft.TypeSpec.Generator, which contains the GeneratorPlugin base class. Use the package version that matches the version of @typespec/http-client-csharp you are generating with. A minimal .csproj looks like this:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.TypeSpec.Generator.ClientModel" Version="X.Y.Z" />
  </ItemGroup>

</Project>

Using the `plugins` option

Once you have a plugin, point the emitter at it using the plugins option in tspconfig.yaml. Each entry is a path that may be absolute or relative to the resolved emitter-output-dir. A path can point to either:

  • a file — a pre-built plugin assembly (*.dll), or
  • a directory containing pre-built plugin assemblies (*.dll) or a .csproj — when a .csproj is present the generator builds it (dotnet build -c Release) before loading the resulting assembly.

For example, to load plugins that live in a codegen folder under the output directory:

yaml
emit:
  - "@typespec/http-client-csharp"
options:
  "@typespec/http-client-csharp":
    plugins:
      - "codegen/MyPlugin.dll" # file relative to emitter-output-dir
      - "codegen" # directory containing plugin assemblies
      - "/abs/path/to/MyPlugin.dll" # absolute path used as-is

Notes:

  • Absolute paths are used as-is; relative paths are anchored to the resolved emitter-output-dir.
  • When a directory contains a .csproj, it is built automatically; otherwise the directory is scanned for pre-built *.dll files.
  • Plugins loaded via the plugins option are applied in addition to any plugins discovered through node_modules.

After the assemblies are loaded, every discovered GeneratorPlugin has its Apply method invoked on the selected generator before generation runs, so all of your registered visitors, rewriters, and references take effect.


Packages Http Server Csharp Docs Emitter (`packages/http-server-csharp/docs/emitter.md`)


title: "Emitter usage"

Emitter Usage

  1. Via the command line
bash
tsp compile . --emit=@typespec/http-server-csharp
  1. Via the config
yaml
emit:
  - "@typespec/http-server-csharp"

The config can be extended with options as follows:

yaml
emit:
  - "@typespec/http-server-csharp"
options:
  "@typespec/http-server-csharp":
    option: value

Emitter options

`skip-format`

Type: boolean

Skips formatting of generated C# Types. By default, C# files are formatted using 'dotnet format'.

`output-type`

Type: "models" | "all"

Chooses which service artifacts to emit. choices include 'models' or 'all' artifacts.

`emit-mocks`

Type: "mocks-and-project-files" | "mocks-only" | "none"

Emits mock implementations of business logic, setup code, and project files enabling the service to respond to requests before a real implementation is provided

`use-swaggerui`

Type: boolean

Configure a Swagger UI endpoint in the development configuration

`openapi-path`

Type: string

Use openapi at the given path for generating SwaggerUI endpoints. By default, this will be 'openapi/openapi.yaml' if the 'use-swaggerui' option is enabled.

`overwrite`

Type: boolean

When generating mock files, setup code, and project files, overwrite any existing files with the same name.

`project-name`

Type: string

The name of the generated project.

`http-port`

Type: number

The service http port when hosting the project locally.

`https-port`

Type: number

The service https port when hosting the project locally.


Packages Http Server Csharp Docs Usage (`packages/http-server-csharp/docs/usage.md`)


title: Generated C-Sharp Overview

Generated C-Sharp Overview

Layout

The code layout inside the 'generated' folder is as follows:

  • generated

    • controllers: A set of ASP.Net core MVC controllers representing the operations in the spec, one for each interface or namespace with operations
    • lib: A set of library files used in implementing generated models and controllers
    • models: A set of models, each serving one of two distinct purposes:
      • Request/Response Models: Represent the data used in requests and responses.
      • Exception Models: Represent errors. These models inherit from HttpServiceException and are designed to encapsulate error details, making them suitable for throwing and handling exceptions.
    • operations: A set of interfaces called by the controllers, that should be implemented with the business logic for each operation.

    You should recompile whenever you make changes in your TypeSpec and these files will be replaced inline to reflect the spec changes, without changing any of your hand-written implementation in the project.

Scaffolding

If you use the scaffolding cli (hscs) or use the --emit-mocks "mocks-and-project-files" option on compilation, a
fully-functional .Net 9 project will be created with mock implementations of your business
logic, ready to compile and run.

The following additional files will be generated. It is expected that you will edit or replace these
files as you implement your service, so you should only regenerate them when needed.
To protect from inadvertently changing any edits you may have made to these files,
these files will be overwritten by the emitter unless you specify the --overwrite option.

  • ServiceProject.csproj: The project file
  • Program.cs: Entry point that sets up the app
  • appSettings.Development.json: Configuration settings for the development environment
  • appSettings.json: Configuration settings for the production environment
  • Properties
    • launchSettings.json: Launch configurations for the service (including local ports)
  • mocks: Simple implementations of business logic interfaces that return simple responses.
    this allows testing your service out before writing any implementation code.
    • MockRegistration.cs: Called from the Program.cs startup, registers each of the business
      logic implementations in the dependency injection container.
    • IInitializer.cs: Interface used in the mocks to create responses.
    • Initializer.cs: Implementation of the interface to create mock responses.

SwaggerUI

If you include the @typespec/openapi3 emitter in your typespec project, you can include a
SwaggerUI endpoint in the generated service using the --use-swaggerui option. This endpoint
provides a visual representation of operations and provides a web GUI client connected to the service that you can use right away to try out service operations.

How Components Work Together

Controllers

The generated controllers automatically listen at the routes you specified in TypeSpec. Controllers perform validation of input requests, call your implementation of business logic interfaces to perform the operation, and return the appropriate Http response.

Business Logic Interfaces

You must implement business loginc interfaces to perform the work of each operation. There is one
business logic interface for each interface type in your spec, or for each namespace that contain operations. Business logic can assume that input types meet the constraints specified in TypeSpec and are responsible for returning the response type for the operation.

You can use the --emit-mocks option to emit mock implementations of your business logic, these mocks demonstrate a simple implementation that returns responses that match the response type in TypeSpec. They also show how to use IHttpContextAccessor to access additional details of the Http request and response.

Discovery using the ASP.Net Core Dependency Injection Container

The Controllers find your business logic implementation through the ASP.Net dependency injection container. At server start, you register each of your implementations with the dependency injection container and they will automatically be instantiated and used by the controllers.

If you use the --emit-mocks option, sample code registering mock implementations is emitted to mocks/MockRegistration.cs.

Models

Model classes represent the data passed in Http requests and response and the data that passes from the front end controllers to your business logic.

Models are partial, so you can add additional members for internal usage as needed by putting a partial class definition with additional members outside the generated folder in your project.

Next Steps

After successful generation, you should:

  • Use the SwaggerUI endpoint to test out the running service
  • Implement the business logic interfaces for your operations
  • Update MockRegistration.cs, or register each of your interfaces as part of application startup
  • Update configuration to suit your needs

Packages Spector Docs Decorators (`packages/spector/docs/decorators.md`)

Spector decorators

`@scenarioService`

Decorator setting up the boilerplate for specs service namespace. Will automatically set:

  • @service{title: '<namespace>', value: '1.0.0'} using the namespace as a value
  • @server to localhost:3000
  • @route using the parameter passed.

Usage:

tsp
@scenarioSpec("/my-spec")
namespace MySpec;

`@scenario`

Mark an operation, interface or namespace as a scenario. Optionally can provide the name of the scenario.

`@scenarioDoc`

Specify how to implement this scenario. Value is markdown. Differ from @doc which describe the scenario to the end user.


Packages Spector Docs Using Spector (`packages/spector/docs/using-spector.md`)

Using spector

Install spector CLI globally or as a local dependency:

  • Globally: run npm install @typespec/spector. You can then use tsp-spector as a CLI tool.
  • Locally: run npm install @typespec/spector which should add the dependency to package.json. You can then use tsp-spector with npx tsp-spector.

NOTE With local install you'll need to prefix tsp-spector with npx(unless commands are written directly as npm scripts)

Test against scenarios

Spector Config

Spector supports a config file where you can configure scenarios that are not supported by your generator.

Create a spector-config.yaml file.

yaml
# List of unsupported scenarios
unsupportedScenarios:
  - Foo_Bar

Run mock api server

bash
# Minimal
tsp-spector serve ./path/to/scenarios

# Change the port
tsp-spector serve ./path/to/scenarios --port 1234

# Specify where the coverage file should go
tsp-spector serve ./path/to/scenarios --coverageFile ./path/to/spector-coverage.json

[!NOTE]
The server always binds to the loopback interface (127.0.0.1), so it is only reachable from the local host and is not exposed to other machines on the network.

Alternative to start in background

bash
tsp-spector server start ./path/to/scenarios # Takes the same arguments as serve

Stop running server

bash
tsp-spector server stop             # Stop at the default port
tsp-spector server stop --port 1234 # If started the server at another port

Validate and merge coverage

bash
# Minimal
tsp-spector check-coverage ./path/to/scenarios

# Path to tsp-spector config file for generator
tsp-spector check-coverage ./path/to/scenarios --configFile ./spector-config.yaml

# In case where there was multiple serve instance each creating their own coverage file
tsp-spector check-coverage ./path/to/scenarios --coverageFiles ./path/to/*-coverage.json --coverageFiles ./other/to/*-coverage.json

# Specify where the merged coverage file should go
tsp-spector check-coverage ./path/to/scenarios --mergedCoverageFile ./path/to/spector-final-coverage.json

Upload coverage

Upload the coverage. Upload from the main branch. DO NOT upload on PR this WILL override the latest index.

bash
# Minimal
tsp-spector upload-coverage --generatorName typescript --version=0.1.0

# Specify Coverage file
tsp-spector upload-coverage --generatorName typescript --version=0.1.0 --coverageFile ./path/to/spector-final-coverage.json

Options:

  • --storageAccountName: Name of the storage account to publish coverage. Use typespec for Spector/Azure Spector dashboard.
  • --generatorName: Name of the generator. Must be one of "@typespec/http-client-python", "@typespec/http-client-csharp", "@azure-tools/typespec-ts-rlc", "@azure-tools/typespec-ts-modular", "@typespec/http-client-js", "@typespec/http-client-java".
  • --generatorVersion: Version of the generator
  • --coverageFile: Path to the coverage file

Upload in azure devops

This is applicable in the azure-sdk/internal ado project only.

Add the following step

yaml
- task: AzureCLI@2
  displayName: Upload scenario coverage
  inputs:
    azureSubscription: "Typespec Storage"
    scriptType: "bash"
    scriptLocation: "inlineScript"
    inlineScript: `tsp-spector upload-coverage --storageAccountName typespec --containerName coverages --generatorMode standard  ... FILL options fitting your generator here as described above...`

Packages Spector Docs Writing Mock Apis (`packages/spector/docs/writing-mock-apis.md`)

Writing mock apis

  1. Create a mockapi.ts file next to the main.tsp of the scenario
  2. Create and export a variable called Scenarios with a Record<string, ScenarioMockApi> type
  3. For each of the scenario assign a new property to the Scenarios variable. The value use one of the following:
    • passOnSuccess: This will take one or multiple routes and will only pass teh scenario if all the routes gets called with a 2xx success code.

Example

ts
import { passOnSuccess, mockapi } from "@typespec/spec-api";
import { ScenarioMockApi } from "@typespec/spec-api";

export const Scenarios: Record<string, ScenarioMockApi> = {};

Scenarios.Hello_world = passOnSuccess(
  mockapi.get("/hello/world", () => {
    return {
      status: 200,
      body: {
        contentType: "application/json",
        rawContent: `"Hello World!"`,
      },
    };
  }),
);

How to build response

Return the response object. See type

ts
// Minimum requirement is the status code.
return {
  status: 200,
};

Return a body

ts
// Return json
return {
  status: 200,
  body: json({ foo: 123 }),
};

// Return raw content
return {
  status: 200,
  body: {
    contentType: "application/text",
    rawContent: "foobar",
  },
};

Return headers

ts
// Return json
return {
  status: 200,
  headers: {
      MyHeader: "value-1"
      MyHeaderOther: req.headers.MyRequestHeader
  }
};

How to validate the request:

All built-in validation tools can be accessed using req.expect.

Validate the body

  • With req.expect.bodyEquals

This will do a deep equals of the body to make sure it match.

ts
app.post("/example", "Example", (req) => {
  req.bodyEquals({ foo: "123", bar: "456" });
});
  • With req.expect.rawBodyEquals

This will compare the raw body sent.

ts
app.post("/example", "Example", (req) => {
  req.rawBodyEquals('"foo"');
});

Custom validation

You can do any kind of validation accessing the req: MockRequest object and deciding to return a different response in some cases.
You can also always throw a ValidationError

Example:

ts
app.post("/example", "Example", (req) => {
  if (req.headers.MyCustomHeader.startsWith("x-foo")) {
    throw new ValidationError(
      "MyCustomHeader shouldn't start with x-foo",
      null,
      req.headers.MyCustomHeader,
    );
  }
});

Packages Spector Docs Writing Scenario Spec (`packages/spector/docs/writing-scenario-spec.md`)

Defining scenario

The goal of the testserver is to define scenarios that needs to be supported in client generators. This means to have a meaningful coverage we need scenarios to:

  • have a specific behavior that is meant to be tested
    • ❌ DO NOT use the same scenario for testing multiple things.
  • have a meaningful name that can be used in a compatibility table to see what a given generator support(e.g. get_string)
  • a description of what the scenario is validating(e.g. "Support passing a simple string as JSON")
  • have a good description on what the client is expecting to generate/receive/send(e.g Validate that this operation returns a JSON string that match "abc")
    • ✅ DO describe how to validate that this scenario is working from the client point of view

When naming scenario always think about what would it look like in the compatibility table and would that name be meaningful to someone looking to see what is supported.

cadl
import "@typespec/spector";

@scenarioService("/strings")
namespace String;

@scenario("get_string")
@doc("Support passing a simple string as JSON")
@scenarioDoc("In this scenario the Client should expect a string matching 'abc' to be returned.")
@get
@route("/simple")
op returnString(): string;

Decorators that should be provided in this test library @typespec/spector:

  • @scenario: Specify that this operation, interface or namespace is a scenario. Optionally take a scenario name otherwise default to the namespace name + operation/interface name
  • @scenarioDoc: Specify how to implement this scenario. Differ from @doc which describe the scenario to the end user.
  • @supportedBy: Specify if something is supported only by some kind of SDK. Option: arm, dpg. By default everything.

Compatibility table

With all this information, a detailed compatibility table should be able to be produced by compiling each one of the scenarios and extracting the cases. Providing something like

Scenario CSharp Python Go Java TS/JS
Vanilla
get_string ✅ ✅ ✅ ✅ ✅
put_string ✅ ✅ ✅ ✅ ✅
Azure
pageable_nextLink ✅ ✅ ✅ ✅ ✅
pageable_continuationToken ❌ ✅ ✅ ✅ ✅

Writing the mock api

Each scenario should be accompanied by a mock api. See writing a mock api docs


Packages Http Client Csharp .Tspd Docs Customization (`packages/http-client-csharp/.tspd/docs/customization.md`)

Customizing the generated code

Before You Customize

Before customizing generated code, consider whether your change should be made in TypeSpec (client.tsp) instead. TypeSpec customizations allow you to apply changes to multiple languages at once, are more discoverable for other languages, and live right near the spec bringing us more to a single source of truth. See the TypeSpec Client Customizations Reference for available decorators like @@clientName, @@access, etc.

Use C# code customizations (partial classes) when TypeSpec cannot express the behavior you need.

Optional nullable model properties

For a TypeSpec property such as feature?: Feature | null, generated model classes distinguish an omitted property from an explicitly assigned null during JSON serialization. Leaving the property untouched omits it; assigning null writes "feature": null; assigning a value writes that value. Reading JSON preserves the same distinction when the model is serialized again, including nested models and wire-format serialization.

For nullable additional-property values, such as Record<string | null>, JSON null entries are retained in AdditionalProperties and included when the model is serialized again, including in wire format.

XML serialization omits null optional properties, including collections, rather than emitting explicit-null elements. Writing XML does not change their JSON presence state.

Presence tracking does not change the public property type or constructor/model-factory signatures. Defaulted constructor and model-factory arguments retain their existing behavior: a scalar null argument is treated as omitted, since C# cannot distinguish an omitted argument from an explicitly supplied default value. Model factories also retain their existing collection initialization behavior, which can materialize a defaulted list argument as an empty list. To write explicit null for a writable property, assign the property after construction.

A handwritten property or field that replaces a generated member (including a [CodeGenMember] replacement) retains its existing customization behavior; the generator cannot observe assignments inside a handwritten setter. There is no presence-tracking opt-in for replacement members. Models customized as readonly structs also retain their existing constructor-only behavior, because readonly structs cannot contain the mutable backing fields and presence flags used by generated classes. Keep the generated class property when its omitted/null distinction is needed. Similarly, plugins that replace generated property bodies or serialization methods must preserve the tracking behavior themselves rather than relying on a replacement auto-property.

Make a model internal

Define a class with the same namespace and name as generated model and use the desired accessibility.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    internal partial class Model { }
}

Generated code after (Generated/Models/Model.cs):

diff
namespace Azure.Service.Models
{
-    public partial class Model { }
+    internal partial class Model { }
}

Rename a model class

Define a class with a desired name and mark it with [CodeGenType("OriginalName")]

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (NewModelClassName.cs)

c#
namespace Azure.Service.Models
{
    [CodeGenType("Model")]
    public partial class NewModelClassName { }
}

Generated code after (Generated/Models/NewModelClassName.cs):

diff
namespace Azure.Service.Models
{
-    public partial class Model { }
+    public partial class NewModelClassName { }
}

Change a model or client namespace

Define a class with a desired namespace and mark it with [CodeGenType("OriginalName")].

The same works for a client, if marked with [CodeGenType("ClientName")].

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (NewModelClassName.cs)

c#
namespace Azure.Service
{
    [CodeGenType("Model")]
    public partial class Model { }
}

Generated code after (Generated/Models/NewModelClassName.cs):

diff
- namespace Azure.Service.Models
+ namespace Azure.Service
{
    public partial class Model { }
}

Make model property internal

Define a class with a property matching a generated property name but with desired accessibility.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        internal string Property { get; }
    }
}

Generated code after (Generated/Models/Model.cs):

diff
namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
    }
}

Rename a model property

Define a partial class with a new property name and mark it with [CodeGenMember("OriginalName")] attribute.

NOTE: you can also change a property to a field using this mapping.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        [CodeGenMember("Property")]
        public string RenamedProperty { get; }
    }
}

Generated code after (Generated/Models/Model.cs):

diff
namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
+        // All original Property usages would reference a RenamedProperty
    }
}

Change a model property type

:warning:

NOTE: This is supported for a narrow set of cases where the underlying serialized type doesn't change

Scenarios that would work:

  1. String <-> TimeSpan (both represented as string in JSON)
  2. Float <-> Int (both are numbers)
  3. String <-> Enums (both strings)
  4. String -> Uri
  5. String -> Guid (both strings)

Won't work:

  1. String <-> Bool (different json type)
  2. Changing model kinds

If you think you have a valid re-mapping scenario that's not supported file an issue.

:warning:

Define a property with different type than the generated one.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public DateTime Property { get; }
    }
}

Generated code after (Generated/Models/Model.Serializer.cs):

diff
namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
+        // Serialization code now reads and writes DateTime value instead of string
    }
}

Preserve raw Json value of a property

Use the Change a model property type approach to change property type to JsonElement.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public JsonElement Property { get; }
    }
}

Generated code after (Generated/Models/Model.Serializer.cs):

diff
namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
+        // Serialization code now reads and writes JsonElement value instead of string
    }
}

Changing member doc comment

Redefine a member in partial class with a new doc comment.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        /// Subpar doc comment
        public string Property { get; }
    }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        /// Great doc comment
        public string Property { get; }
    }
}

Generated code after (Generated/Models/Model.cs):

diff
namespace Azure.Service.Models
{
    public partial class Model
    {
-        /// Subpar doc comment
-        public string Property { get; }
    }
}

Customize serialization/deserialization methods

Changing how a property serializes or deserializes is done by CodeGenSerialization attribute. This attribute can be applied to a class or struct with a property name to change the serialization/deserialization method of the property.

Change the serialized name of a property

If you want to change the property name that serializes into the JSON or deserializes from the JSON, you could define your own partial class with the CodeGenMemberSerialization attribute.

For instance, we have a model class Cat with property Name and Color:

Generated code before:

c#
// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

Add customized model:

c#
// Cat.cs
namespace Azure.Service.Models
{
    [CodeGenSerialization(nameof(Name), "catName")] // add the property name, and the new serialized name
    public partial class Cat
    {
    }
}

Generated code after:

diff
// Generated/Models/Cat.cs - no change

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
-           writer.WritePropertyName("name"u8);
+           writer.WritePropertyName("catName"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
-               if (property.NameEquals("name"u8))
+               if (property.NameEquals("catName"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Change the hierarchy of a property in the serialized JSON

If you want to change the layer of the property in the json, you can add all the elements in the json path of your property to the attribute using an array, the generator will generate the property into the JSON in the correct hierarchy.

NOTE: Introducing extra layers in serialized JSON only works for MPG and HLC models, does not work for DPG models.

For instance, we want to move Name property in the model Cat to make it serialized under property properties and rename to catName.

Generated code before:

c#
// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Add customized model:

c#
// Cat.cs
namespace Azure.Service.Models
{
    [CodeGenSerialization(nameof(Name), new string[] { "properties", "catName" })]
    public partial class Cat
    {
    }
}

Generated code after:

text
/* Detailed source-code truncated for AI context efficiency. */

Change the implementation of serialization/deserialization method of a property

If you want to change the implementation of serialization/deserialization method of a property, you could define your own hook methods and assign them to the CodeGenSerialization attribute.

The custom serialization method for this property is assigned by the SerializationValueHook property of the CodeGenSerialization attribute, and the custom deserialization method for this property is assigned by the DeserializationValueHook property of the CodeGenSerialization attribute.

The SerializationValueHook and DeserializationValueHook here are hook method names, and these methods should have the signature as below:

c#
// serialization hook and serialization value hook
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SerializationMethodHook(Utf8JsonWriter writer)
{
    // write your own serialization logic here
}

// deserialization hook for required property
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void DeserializeSizeProperty(JsonProperty property, ref TypeOfTheProperty name)
{
    // write your own deserialization logic here
}
// deserialization hook for optional property
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void DeserializeSizeProperty(JsonProperty property, ref Optional<TypeOfTheProperty> name)
{
    // write your own deserialization logic here
}

Please use the nameof expression to avoid typo in the attribute. Also you could leave both the serialization value hook unassigned if you do not want to change the serialization logic, similar you could leave deserialization hook unassigned if you do not want to change the deserialization logic.

The [MethodImpl(MethodImplOptions.AggressiveInlining)] attribute is recommended for your hook methods to get optimized performance.

Please note that the generator will not check the signature of the hook methods you assigned to the attribute, therefore if the signature is not compatible, the generated library might not compile.

For instance, we have a model class Cat with property Name and Color, and we would like to change the way how Name property is serialized and deserialized.

Generated code before:

c#
// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Add customized model:

c#
// Cat.cs
namespace Azure.Service.Models
{
    [CodeGenSerialization(nameof(Name), SerializationValueHook = nameof(SerializeNameValue), DeserializationValue = nameof(DeserializeNameValue))]
    public partial class Cat
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SerializeNameValue(Utf8JsonWriter writer)
        {
            // this is the logic we would like to have for the value serialization
            writer.WriteStringValue(Name.ToUpper());
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void DeserializeNameValue(JsonProperty property, ref string name) // the type here is string since name is required
        {
            // this is the logic we would like to have for the value deserialization
            name = property.Value.GetString().ToLower();
        }
    }
}

Generated code after:

diff
// Generated/Models/Cat.cs - no change

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
-           writer.WriteStringValue(Name);
+           SerializeNameValue(writer);
            if (Optional.IsDefined(Color))
            {
                writer.WritePropertyName("color"u8);
                writer.WriteStringValue(Color);
            }
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
-                   meow = property.Value.GetString();
+                   DeserializeNameValue(property, ref name);
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color, size);
        }
    }
}

Add a new property to the model with serialization/deserialization

If you want to add a new property to the model and also add the property into the serialization/deserialization methods, you could also use the CodeGenSerialization attribute to change its default serialized name, and serialization/deserialization methods.

Generated code before:

c#
// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Add customized model:

c#
[CodeGenSerialization(nameof(Size), "size")]
public partial class Cat
{
    public int? Size { get; set; }
}

Generated code after:

text
/* Detailed source-code truncated for AI context efficiency. */

You could also add the CodeGenSerialization attribute to the property to have your own serialization/deserialization logic of the new property. You might have to do this if the type of your new property is an object type or any type that our generator does not natively support.

NOTE: Adding property to serialization/deserialization methods currently only works for DPG.

Replace the entire serialization/deserialization method

If you want to replace the entire serialization/deserialization method, please use the Replace any generated member approach to replace serialization/deserialization method with a custom implementation.

Generated code before:

c#
// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            if (Optional.IsDefined(Color))
            {
                writer.WritePropertyName("color"u8);
                writer.WriteStringValue(Color);
            }
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            string color = default;
            string name = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("color"u8))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        continue;
                    }
                    color = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("name"u8))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        continue;
                    }
                    name = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(id, name);
        }
    }
}

Add customized model:

c#
// Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        // currently we have to use a full name to ensure this could be replaced
        void global:Azure.Core.IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            // WORKAROUND: server never needs color, remove it in the customization code
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            string color = default;
            string name = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        continue;
                    }
                    name = property.Value.GetString();
                    continue;
                }
            }
            // WORKAROUND: server never sends color, default to black
            color = "black";
            return new Cat(name, color);
        }
    }
}

Generated code after:

Generated code won't contain the IUtf8JsonSerializable.Write or DeserializeCat method and the custom one would be used for deserialization.

Renaming an enum

Redefine an enum with a new name and all the members mark it with [CodeGenType("OriginEnumName")].

NOTE: because enums can't be partial all values have to be copied

Generated code before (Generated/Models/Colors.cs):

c#
namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green,
        Blue
    }
}

Add customized model (WallColors.cs)

c#
namespace Azure.Service.Models
{
    [CodeGenType("Colors")]
    public enum WallColors
    {
        Red,
        Green,
        Blue
    }
}

Generated code after (Generated/Models/Model.cs):

diff
-namespace Azure.Service.Models
-{
-    public enum Colors
-    {
-        Red,
-        Green,
-        Blue
-    }
-}
+// Serialization code uses the new WallColors type name

Renaming an enum member

Redefine an enum with the same name and all the members, mark renamed member with [CodeGenMember("OriginEnumMemberName")].

NOTE: because enums can't be partial all values have to be copied but only the ones being renamed should be marked with an attributes

Generated code before (Generated/Models/Colors.cs):

c#
namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green,
        Blue
    }
}

Add customized model (Colors.cs)

c#
namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green,
        [CodeGenMember("Blue")]
        SkyBlue
    }
}

Generated code after (Generated/Models/Model.cs):

diff
-namespace Azure.Service.Models
-{
-    public enum Colors
-    {
-        Red,
-        Green,
-        Blue
-    }
-}
+// Serialization code uses the new SkyBlue member name

Changing an enum to an extensible enum

Redefine an enum into an extensible enum by creating an empty struct with the same name as original enum.

Generated code before (Generated/Models/Colors.cs):

c#
namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green
    }
}

Add customized model (Colors.cs)

c#
namespace Azure.Service.Models
{
    public partial struct Colors
    {
    }
}

Generated code after (Generated/Models/Model.cs):

diff
namespace Azure.Service.Models
{
-    public enum Colors
-    {
-        Red,
-        Green
-    }
+    public readonly partial struct Colors : IEquatable<Colors>
+    {
+        private readonly string _value;

+        public Colors(string value)
+        {
+            _value = value ?? throw new ArgumentNullException(nameof(value));
+        }

+        private const string Red = "red";
+        private const string Green = "green";

+        public static Colors Red { get; } = new Colors(Red);
+        public static Colors Green { get; } = new Colors(Green);
+        public static bool operator ==(Colors left, Colors right) => left.Equals(right);
         ...
}

Make a client internal

Define a class with the same namespace and name as generated client and use the desired accessibility.

Generated code before (Generated/Operations/ServiceClient.cs):

c#
namespace Azure.Service.Operations
{
    public partial class ServiceClient { }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Operations
{
    internal partial class ServiceClient { }
}

Generated code after (Generated/Operations/ServiceClient.cs):

diff
namespace Azure.Service.Operations
{
-    public partial class ServiceClient { }
+    internal partial class ServiceClient { }
}

Rename a client

Define a partial client class with a new name and mark it with [CodeGenType("OriginalName")]

Generated code before (Generated/Operations/ServiceClient.cs):

c#
namespace Azure.Service.Operations
{
    public partial class ServiceClient {}
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Operations
{
    [CodeGenType("ServiceClient")]
    public partial class TableClient { }
}

Generated code after (Generated/Operations/ServiceClient.cs):

diff
namespace Azure.Service.Operations
{
-    public partial class ServiceClient { }
+    public partial class TableClient { }
}

Customize a generated client method's signature

Declare a partial method (without a body) on the generated client class with the same name and parameter types as a generated protocol or convenience method. The generator emits a matching partial implementation, taking the modifiers and parameter names from your declaration while keeping its generated body.

This applies specifically to client methods (protocol and convenience methods on the generated *Client class). Other generated members (model constructors, model serialization methods, model factories, etc.) should be customized using [CodeGenSuppress] + a hand-written replacement, or one of the other techniques in this document.

Use this when you want to keep the generated body but tweak the surface — typically:

  • Tighten the access modifier (public → internal).
  • Add virtual / override / new modifiers.
  • Rename a parameter to something more idiomatic.

This is preferred over [CodeGenSuppress] + a hand-written replacement when the only thing you want to change is the signature, because you stay in lock-step with the generated body — future regenerations automatically pick up changes (new optional parameters, body changes from spec edits, etc.).

What is supported

  • Modifier changes (access modifier, virtual, override, new, unsafe).
  • Parameter renames. The generator clones each parameter with the customer-chosen name while preserving all internal metadata (parameter location, wire info, spread source, validation, …) so the generated body keeps compiling.
  • Both protocol method overloads and convenience method overloads (sync and async) on the generated client. Each overload must be customized independently.

What is NOT supported

  • Renaming the method itself. Matching is by (method name, ordered parameter type list). To rename, use [CodeGenSuppress] + a hand-written method that delegates to the underlying request/pipeline machinery.
  • Changing parameter types. The parameter type list must match the generated signature exactly (matched by type name). Replacing a parameter type with a different type — even an implicitly convertible one — will simply fail to match and the partial decl will be ignored. To project a different type, use [CodeGenSuppress] + a hand-written wrapper that converts and forwards.
  • Adding/removing/reordering parameters. The parameter type list must match the generated signature exactly. To restructure, use [CodeGenSuppress].
  • Default values on the partial implementation. C# requires partial method implementations to have all parameters required, so any default values on your partial declaration are stripped on the generated impl. Callers still see the defaults from your declaration in custom code.
  • Non-client members (models, serialization methods, model factories). Use [CodeGenSuppress] for these.

Generated code before (Generated/TestClient.cs):

c#
namespace Azure.Service
{
    public partial class TestClient
    {
        public virtual ClientResult HelloAgain(BinaryContent p1, RequestOptions options = null)
        {
            Argument.AssertNotNull(p1, nameof(p1));
            using PipelineMessage message = CreateRequest(p1, options);
            return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options));
        }
    }
}

Add customized client (TestClient.cs):

c#
namespace Azure.Service
{
    public partial class TestClient
    {
        // Renames `p1` to `content` and changes accessibility from public to internal.
        internal partial ClientResult HelloAgain(BinaryContent content, RequestOptions options);
    }
}

Generated code after (Generated/TestClient.cs):

diff
namespace Azure.Service
{
    public partial class TestClient
    {
-        public virtual ClientResult HelloAgain(BinaryContent p1, RequestOptions options = null)
+        internal partial ClientResult HelloAgain(BinaryContent content, RequestOptions options)
        {
-            Argument.AssertNotNull(p1, nameof(p1));
-            using PipelineMessage message = CreateRequest(p1, options);
+            Argument.AssertNotNull(content, nameof(content));
+            using PipelineMessage message = CreateRequest(content, options);
            return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options));
        }
    }
}

Replace any generated member

Works for model and client properties, methods, constructors etc.

Define a partial class with member with the same name and for methods same parameters.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public Model()
        {
            Property = "a";
        }

        public string Property { get; set; }
    }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        internal Model()
        {
            Property = "b";
        }
    }
}

Generated code after (Generated/Models/Model.cs):

diff
namespace Azure.Service.Models
{
    public partial class Model
    {
-        public Model()
-        {
-            Property = "a";
-        }
    }
}

Remove any generated member

Works for model and client properties, methods, constructors etc.

Define a partial class with [CodeGenSuppress("NameOfMember", typeof(Parameter1Type), typeof(Parameter2Type))] attribute.

Generated code before (Generated/Models/Model.cs):

c#
namespace Azure.Service.Models
{
    public partial class Model
    {
        public Model()
        {
            Property = "a";
        }

        public Model(string property)
        {
            Property = property;
        }

        public string Property { get; set; }
    }
}

Add customized model (Model.cs)

c#
namespace Azure.Service.Models
{
    [CodeGenSuppress("Model", typeof(string))]
    public partial class Model
    {
    }
}

Generated code after (Generated/Models/Model.cs):

diff
namespace Azure.Service.Models
{
    public partial class Model
    {
-        public Model(string property)
-        {
-            Property = property;
-        }
    }
}

Extending a model with additional constructors

As with most customization, you can define a partial class for Models and extend them with methods and constructors.

Generated code before (Generated/Models/Model.cs):

csharp
namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (Model.cs)

csharp
namespace Azure.Service.Models
{
    public partial class Model {
        public Model(int x)
        {
        }
    }
}

Packages Http Client Csharp .Tspd Docs Usage (`packages/http-client-csharp/.tspd/docs/usage.md`)

Prerequisite

  • Install Node.js 20 or above. (Verify by running node --version)
  • Install .NET 8.0 SDK for your specific platform. (or a higher version)

Customizing Generated Code

For detailed instructions on how to customize the generated C# code, see the Customization Guide.

Experimental types and members

Use @TypeSpec.HttpClient.experimental to assign a public diagnostic ID to a generated
type or member and identify experiments used by its implementation:

typespec
import "@typespec/http-client";

@TypeSpec.HttpClient.experimental(#{
  emitterScope: "@typespec/http-client-csharp",
  diagnosticId: "C",
  dependsOn: #["A", "B"],
})
op bar(): void;

The C# emitter adds [Experimental("C")] to the corresponding generated declaration:

TypeSpec target Generated C# target
Model Model class or struct
Model property Property
Enum or union emitted as an enum Enum or extensible-enum struct
Enum member or union variant emitted as an enum value Enum field or extensible-enum property
Operation Synchronous and asynchronous protocol and convenience methods
Namespace or interface emitted as a client Client class

For example, models and their properties can be separate experiments:

typespec
@TypeSpec.HttpClient.experimental(#{ diagnosticId: "MODEL001" })
model Preview {
  @TypeSpec.HttpClient.experimental(#{ diagnosticId: "PROPERTY001" })
  value?: string;
}

Model factory methods expose the model's diagnostic. Partial serialization declarations
do not repeat the model attribute. Generated code suppresses diagnostics when referring
to source-annotated experimental types and members, so serialization, factories, and client
implementations can compile without changing the experimental status of other public APIs.

Suppressions surround only the member declarations or serialization blocks that need them,
including parameter and return types, generic arguments, and implementation references.
Explicit type-level dependsOn entries and base-type dependencies can require a type-wide
scope. References within a declaration carrying the same experimental diagnostic ID,
including its other partial declarations, do not need an additional suppression.
These suppressions do not affect diagnostics in consumer code or separate custom files.

Dependencies identify diagnostics rather than individual types: one diagnostic can apply
to multiple types or members, including those defined in external libraries. For externally
mapped types, source @experimental metadata is retained for generated-reference suppressions;
the emitter does not generate the external declaration or add attributes to its library.
This also covers client parameters exposed through generated settings and options properties.
Other external experiments require explicit dependsOn entries. They are not discovered by reflection.

Both metadata fields are optional. Without diagnosticId, no public experimental attribute
is added. Without dependsOn, no additional dependency diagnostics are requested; generated
references to source-annotated experiments are still handled. Emitter scopes apply to both fields.

Diagnostic IDs must be single C# warning identifiers (ASCII letters, digits, and underscores,
not starting with a digit) or decimal warning numbers. Whitespace, punctuation, comments, and
line breaks are rejected with invalid-experimental-diagnostic-id before generating C#.
An ExperimentalAttribute on a customized partial client or method takes precedence over
the generated attribute, including a polyfilled System.Diagnostics.CodeAnalysis.ExperimentalAttribute
on older target frameworks. Generated parent-client accessors suppress a child client's custom
diagnostic without marking the parent or accessor as experimental.

Some TypeSpec declarations have no corresponding C# declaration, such as scalars or unions
erased to built-in C# types without an explicit external mapping. C# also does not allow ExperimentalAttribute on parameters.
The emitter reports experimental-target-not-supported for these annotations instead of
silently dropping them or assigning their diagnostic to an unrelated API.

Models referenced by a union retain their own experimental metadata. Annotate the model
declaration, not the union variant that references it: a model variant has no separate C#
declaration on which to place an attribute.

Graduation is an explicit source change: dependencies becoming generally available, or
removing entries from dependsOn, does not remove [Experimental("C")]. Remove the
declaration's @experimental decorator when the public API is ready to graduate.


Packages Http Client Csharp Generator Docs Architecture (`packages/http-client-csharp/generator/docs/architecture.md`)

Generator Architecture

Table of Contents

Architecture Overview

The TypeSpec Generator follows a layered architecture designed for extensibility and maintainability:

text
/* Detailed source-code truncated for AI context efficiency. */

Core Components

1. **Emitter Processing Layer**

  • TypeSpec Emitter: TypeScript-based emitter that interfaces with the TypeSpec compiler
    • Parses TypeSpec API definitions and emitter options
    • Generates configuration.json with generator settings and emitter options
    • Creates tspCodeModel.json containing the serialized TypeSpec model
    • Invokes the Microsoft.TypeSpec.Generator.dll with translated command-line arguments
    • Serves as the bridge between TypeSpec tooling and the C# generator

2. **Input Processing Layer**

  • Microsoft.TypeSpec.Generator.Input: Handles deserialization of TypeSpec JSON output
  • InputLibrary: Entry point for loading and accessing TypeSpec model data
  • InputTypes: Strongly-typed representations of TypeSpec constructs (models, operations, etc.)

3. **Code Model Generation Layer**

  • CodeModelGenerator: Abstract base class defining the generator contract and extensibility points
  • ScmCodeModelGenerator: Concrete implementation for System.ClientModel-based generators
  • TypeFactory: Factory pattern for creating output type providers from input types
  • GeneratorContext: Provides configuration and runtime context

4. **Output Model Layer**

  • OutputLibrary: Container for all generated type providers
  • TypeProvider: Abstract representation of generated types (models, enums, clients)
  • Providers/: Concrete implementations for different code constructs:
    • ModelProvider: Data models with properties and serialization
    • EnumProvider: Enumeration types
    • MethodProvider: Client methods and operations
    • PropertyProvider: Model properties with accessors

5. **Transformation Pipeline**

  • LibraryVisitor: Visitor pattern for traversing and modifying the output library
  • LibraryRewriter: Advanced transformation capabilities for code modification
  • Plugin System: MEF-based extensibility for custom transformations

6. **Code Generation Layer**

  • Writers/: Responsible for converting providers to actual C# syntax
  • Snippets/: Reusable code patterns and expressions
  • Expressions/: Type-safe representation of C# expressions
  • Statements/: Type-safe representation of C# statements

7. **Configuration & Context**

  • Configuration: Centralized configuration management from JSON input
  • GeneratorContext: Runtime context and dependency injection container
  • SourceInputModel: Integration with existing custom code via Roslyn analysis

8. **Emitter Communication Layer**

  • Emitter: JSON-RPC based communication channel for logging and diagnostics
    • Provides structured logging (info, debug, verbose) back to TypeSpec compiler
    • Reports diagnostics and errors with proper severity levels
    • Enables real-time feedback during code generation process
    • Facilitates integration with TypeSpec tooling and IDE extensions

Generator Pipeline

The generation process follows a well-defined pipeline:

  1. Initialization

    • Load configuration from Configuration.json
    • Initialize generator context and MEF composition
    • Load TypeSpec model from tspCodeModel.json
  2. Input Processing

    • Deserialize TypeSpec JSON into strongly-typed input model
    • Validate and normalize input data
    • Build dependency graphs
  3. Code Model Generation

    • Transform input types to output providers using TypeFactory
    • Apply generator-specific customizations
    • Build method signatures and type hierarchies
  4. Source Integration

    • Analyze existing custom code using Roslyn
    • Merge custom implementations with generated code
    • Respect customization attributes and partial classes
  5. Visitor Pipeline

    • Execute registered visitors in dependency order
    • Apply transformations, validations, and enhancements
    • Support for both built-in and plugin-provided visitors
  6. Code Generation

    • Write providers to CodeFiles containing C# source code
    • Parse CodeFile content into syntax trees for Roslyn processing
    • Apply formatting and style conventions
    • Generate supporting files (model factories, serialization)
  7. Output

    • Write generated files to target directories
    • Preserve custom code and configurations
    • Update project files and dependencies

Extensibility Framework

The generator provides multiple extensibility points:

**Generator Inheritance**

csharp
[Export(typeof(CodeModelGenerator))]
public class CustomGenerator : CodeModelGenerator
{
    public override TypeFactory TypeFactory => new CustomTypeFactory();
    public override OutputLibrary OutputLibrary => new CustomOutputLibrary();
}

**Custom Visitors**

csharp
public class CustomLibraryVisitor : LibraryVisitor
{
    protected override TypeProvider? VisitModel(ModelProvider model)
    {
        // Custom model transformations
        return base.VisitModel(model);
    }
}

**Plugin System**

csharp
[Export(typeof(GeneratorPlugin))]
public class CustomPlugin : GeneratorPlugin
{
    public override void Apply(CodeModelGenerator generator)
    {
        generator.AddVisitor(new CustomVisitor());
    }
}

**Type Factories**

csharp
public class CustomTypeFactory : TypeFactory
{
    public override ModelProvider CreateModel(InputModelType inputModel)
    {
        // Custom model creation logic
        return new CustomModelProvider(inputModel);
    }
}

Testing Strategy

The project employs a comprehensive testing approach:

**Spector Tests**

  • Purpose: Integration testing against HTTP specification test cases
  • Location: TestProjects/Spector/
  • Approach: Generates client libraries for HTTP-specs test cases and validates API surface
  • Stubbed Generation: Minimizes repository size while maintaining API contract testing

**Local Tests**

  • Purpose: Unit and integration testing of generator components
  • Location: TestProjects/Local/
  • Approach: Direct testing of generator functionality with controlled inputs

**Performance Tests**

  • Purpose: Benchmarking generator performance and memory usage
  • Location: Various *.Tests.Perf projects
  • Approach: Measures generation time and resource consumption

**Plugin Tests**

  • Purpose: Validates extensibility framework and plugin system
  • Location: TestProjects/Plugin/
  • Approach: Tests custom generators and plugins

Packages Http Client Csharp Generator Docs Backward Compatibility (`packages/http-client-csharp/generator/docs/backward-compatibility.md`)

Backward Compatibility Support

Table of Contents

Overview

The TypeSpec C# generator supports backward compatibility (back-compat) to ensure that existing client code continues to work when the service API evolves. This is achieved by comparing the current generated code against a previous contract (assembly) and automatically generating compatibility shims where needed.

The backward compatibility system uses the LastContractView mechanism to access information about previously generated types, methods, and properties from the last released version of the library.

How It Works

When generating code, the generator can optionally receive a compiled assembly from the previous version of the library (the "last contract"). The generator:

  1. Analyzes the current TypeSpec specification and generates types based on the current API
  2. Compares the generated types against the types in the last contract
  3. Automatically generates compatibility methods, properties, or enum members where differences are detected

Experimental Types

Types in the last released contract marked with System.Diagnostics.CodeAnalysis.ExperimentalAttribute are excluded from backward-compatibility lookup. The generator does not preserve their previous type shape or restore their previous members as compatibility shims, allowing those types to change when they graduate to stable or are removed.

This decision uses the attribute on the last released type, including polyfilled definitions of the attribute. Marking a previously stable type experimental in the current generation does not remove its compatibility guarantees. Experimental attributes on individual members in the last released contract also exclude those members from compatibility handling; attributes added only in the current generation do not remove guarantees for previously stable members.

ApiCompat Baseline Awareness

Sometimes a breaking change (such as removing a model or a model factory method) is intentional and has already been reviewed and accepted. In the Azure SDK, such accepted breaking changes are recorded in an ApiCompat baseline (suppression) file, conventionally located at eng/apicompatbaselines/<AssemblyName>.txt.

Without awareness of these files, the back-compat system would resurrect every member present in the last contract — re-introducing the very API that was intentionally removed (and potentially referencing types that no longer exist). To prevent this, the generator discovers the baseline file by walking up from the project directory and parses its TypesMustExist, MembersMustExist, and EnumValuesMustMatch suppressions. The back-compat code then consults the baseline in the following ways:

  • Skipping resurrected members: before regenerating a compatibility shim for a removed member, it checks whether the removal has been accepted in the baseline (matched by declaring-type full name, member name, and parameter count) and, if so, skips it.
  • Allowing property type changes: the back-compat system normally preserves a property's previous (last-contract) type to avoid a breaking change. When that previous type — or a type nested within it (e.g. the element type of a collection) — has itself been intentionally removed and accepted in the baseline, preserving it would reference a now-deleted type. In that case the generator allows the property to take its current (spec) type instead.
  • Skipping re-added enum members: before re-adding a fixed enum member that exists in the last contract but was dropped from the current spec, it checks whether that removal — or an accepted value difference recorded as EnumValuesMustMatch — has been accepted in the baseline (matched by the declaring enum's fully-qualified name and the member name) and, if so, omits the member.

[!NOTE]
Baseline awareness is currently wired into the base method back-compat path in TypeProvider (the parameter reorder/removal loop, inherited by ClientProvider and also used directly by ModelFactoryProvider), the model property type-preservation path (ModelProvider), and the fixed enum member path (FixedEnumProvider). The remaining back-compat consumers (ModelProvider constructors and RestClientProvider) can be made baseline-aware in the same way as a follow-up.

Supported Scenarios

Model Factory Methods

Model factory methods are public static methods that enable creating model instances for mocking and testing purposes. The generator attempts to maintain backward compatibility for these methods across API changes.

Scenario: New Model Property Added

Description: When a new property is added to a model, the generator creates a backward-compatible factory method overload that excludes the new parameter.

Example:

Previous version had a model with three properties:

csharp
public static PublicModel1 PublicModel1(
    string stringProp = default,
    Thing modelProp = default,
    IEnumerable<string> listProp = default)

Current version adds a new property (dictProp):

csharp
public static PublicModel1 PublicModel1(
    string stringProp = default,
    Thing modelProp = default,
    IEnumerable<string> listProp = default,
    IDictionary<string, string> dictProp = default)

Generated Compatibility Method:

csharp
[EditorBrowsable(EditorBrowsableState.Never)]
public static PublicModel1 PublicModel1(
    string stringProp,
    Thing modelProp,
    IEnumerable<string> listProp)
{
    return PublicModel1(stringProp, modelProp, listProp, dictProp: default);
}

Key Points:

  • The old method signature is preserved
  • It delegates to the new method with default value for the new parameter
  • Parameters in the compatibility method have no default values to avoid ambiguous call sites
  • The method is marked with [EditorBrowsable(EditorBrowsableState.Never)] to hide it from IntelliSense

Scenario: Parameter Ordering Changed

Description: When only the parameter ordering changes in a factory method (same parameters, different order), the generator replaces the current method with the previous method signature.

Example:

Previous version:

csharp
public static PublicModel1 PublicModel1(
    Thing modelProp = default,
    string stringProp = default,
    IEnumerable<string> listProp = default,
    IDictionary<string, string> dictProp = default)

Current version would generate different ordering:

csharp
public static PublicModel1 PublicModel1(
    string stringProp = default,
    Thing modelProp = default,
    IEnumerable<string> listProp = default,
    IDictionary<string, string> dictProp = default)

Result: The generator keeps the previous parameter ordering to maintain compatibility.

Scenario: Property Renamed

Description: When a model property is renamed (via @@clientName, a spec rename, a generator naming-rule change, etc.), the generated factory parameter would normally change its name to follow the new property name. Renaming a parameter is source-breaking for callers using named arguments and is not flagged by ApiCompat / binary-compat tooling. To avoid this, the generator preserves the previous parameter name on the current factory method whenever the only difference between the previous and current method is one or more parameter names (same method name, same parameter types in the same order, same parameter count).

Example:

Previous version exposed stringProp and modelProp:

csharp
public static PublicModel1 PublicModel1(
    string stringProp = default,
    Thing modelProp = default,
    IEnumerable<string> listProp = default,
    IDictionary<string, string> dictProp = default)

Current TypeSpec renames the underlying properties to CertificateStringProp and CertificateModelProp, which would normally produce:

csharp
public static PublicModel1 PublicModel1(
    string certificateStringProp = default,
    Thing certificateModelProp = default,
    IEnumerable<string> listProp = default,
    IDictionary<string, string> dictProp = default)

Generated Compatibility Result:

The generator detects that previous and current methods differ only in parameter names and preserves the previous names on the current method:

csharp
public static PublicModel1 PublicModel1(
    string stringProp = default,
    Thing modelProp = default,
    IEnumerable<string> listProp = default,
    IDictionary<string, string> dictProp = default)
{
    // body uses the preserved names when constructing the model
    return new PublicModel1(stringProp, modelProp, listProp.ToList(), dictProp, additionalBinaryDataProperties: null);
}

Key Points:

  • Only one method is generated — no additional [EditorBrowsable(EditorBrowsableState.Never)] overload is needed
  • Existing source code using named arguments (e.g. PublicModel1(stringProp: "x")) continues to compile
  • The matching is by method name plus parameter types in the same order; the parameter count must also match. If a parameter is added or removed in addition to a rename, the standard "new property added" overload is generated instead (see scenario below)
  • The XML doc `` entries on the method are updated to reference the preserved names

Scenario: New Property Added Together with a Rename

Description: When a new property is added to a model AND a previously-published property has been renamed at the same time, the parameter counts of the previous and current factory methods differ, so the rename-only fast path above does not apply. The generator falls back to the standard "new property added" flow and emits an [EditorBrowsable(EditorBrowsableState.Never)] backcompat overload using the previously-published parameter names.

[!NOTE]
The body of the backcompat overload constructs the model directly. Renamed parameters whose names no longer match a current property are passed as default to the constructor, since the body can no longer thread them through the renamed properties. This keeps existing source compiling, but callers who depended on the old parameter being forwarded should migrate to the current method.

Example:

Previous version exposed three properties, with the first two under different names:

csharp
public static PublicModel1 PublicModel1(
    string oldStringProp = default,
    Thing oldModelProp = default,
    IEnumerable<string> listProp = default)

Current TypeSpec renames the first two properties (now stringProp / modelProp) and adds a new property (dictProp):

csharp
public static PublicModel1 PublicModel1(
    string stringProp = default,
    Thing modelProp = default,
    IEnumerable<string> listProp = default,
    IDictionary<string, string> dictProp = default)

Generated Compatibility Method:

csharp
[EditorBrowsable(EditorBrowsableState.Never)]
public static PublicModel1 PublicModel1(
    string oldStringProp,
    Thing oldModelProp,
    IEnumerable<string> listProp)
{
    listProp ??= new ChangeTrackingList<string>();

    return new PublicModel1(default, default, listProp.ToList(), default, additionalBinaryDataProperties: null);
}

Key Points:

  • The previous (renamed) signature is preserved so existing source code continues to compile
  • Parameters whose names matched a current property (listProp) are threaded through; renamed parameters are passed as default
  • The new property is also passed as default
  • A separate visible method exposes the current signature with the new property and the renamed parameters

Model Properties

The generator preserves the previous property type whenever it differs from the type produced by the current spec. This applies to all public model properties (scalars, enums, models, and collections), so any property type change is non-source-breaking by default. Users who want the new spec's type to take effect can override this behavior with custom code.

Scenario: Collection Property Type Changed

Description: When a property type changes from a read-only collection to a read-write collection (or vice versa), the generator preserves the previous property type to avoid breaking changes.

Example:

Previous version:

csharp
public IReadOnlyList<string> Items { get; }

Current TypeSpec would generate:

csharp
public IList<string> Items { get; set; }

Result: The generator detects the type mismatch and preserves the previous type:

csharp
public IReadOnlyList<string> Items { get; }

Scenario: Scalar/Model Property Type Changed

Description: When the type of a scalar, enum, or model property differs between the last contract and the current spec, the generator preserves the last contract's type shape. Nullable value-type differences also preserve the last contract's nullability.

Reference types loaded from the last contract are nullable-oblivious, so their top-level nullability is not used as compatibility evidence. The generator retains the current TypeSpec top-level reference nullability while preserving any other last-contract type differences, including nested generic argument types. This prevents compatibility processing from adding null guards or null-unsafe collection conversions that contradict the current wire contract.

Example:

Previous version:

csharp
public int? Count { get; set; }

Current TypeSpec would generate:

csharp
public int Count { get; set; }

Result: The generator detects the type mismatch and preserves the previous nullable type:

csharp
public int? Count { get; set; }

A diagnostic message is logged for every overridden property: "Changed property {ModelName}.{PropertyName} type to {LastContractType} to match last contract."

AdditionalProperties Type Preservation

The generator maintains backward compatibility for the AdditionalProperties property type on models that extend or use Record.

Scenario: AdditionalProperties Type Changed from Object to BinaryData

Description: When a model with additional properties was previously generated with IDictionary<string, object>, but the current generator would produce IDictionary<string, BinaryData>, the generator preserves the previous object type to maintain backward compatibility.

This commonly occurs when:

  • Migrating from an older generator version that used object for unknown additional property values
  • Regenerating a library that was originally created with IDictionary<string, object> for Record types

Example:

Previous version generated with object type:

csharp
public partial class MyModel
{
    private readonly IDictionary<string, object> _additionalBinaryDataProperties;

    public MyModel(string name, IDictionary<string, object> additionalProperties)
    {
        Name = name;
        _additionalBinaryDataProperties = additionalProperties;
    }

    public string Name { get; set; }
    public IDictionary<string, object> AdditionalProperties { get; }
}

Current TypeSpec would generate with BinaryData:

csharp
public partial class MyModel
{
    private readonly IDictionary<string, BinaryData> _additionalBinaryDataProperties;

    public MyModel(string name, IDictionary<string, BinaryData> additionalProperties)
    {
        Name = name;
        _additionalBinaryDataProperties = additionalProperties;
    }

    public string Name { get; set; }
    public IDictionary<string, BinaryData> AdditionalProperties { get; }
}

Generated Compatibility Result:

When the last contract had IDictionary<string, object>, the generator preserves the object type:

csharp
public partial class MyModel
{
    private readonly IDictionary<string, object> _additionalBinaryDataProperties;

    public MyModel(string name, IDictionary<string, object> additionalProperties)
    {
        Name = name;
        _additionalBinaryDataProperties = additionalProperties;
    }

    public string Name { get; set; }
    public IDictionary<string, object> AdditionalProperties { get; }
}

Key Points:

  • Applies to models with AdditionalProperties defined via Record or similar patterns
  • The backing field type is changed from IDictionary<string, BinaryData> to IDictionary<string, object>
  • The property type matches the backing field type to avoid compilation errors
  • Serialization and deserialization automatically handle both object and BinaryData types
  • For object types, deserialization uses JsonElement.GetObject() instead of wrapping in BinaryData
  • For object types, serialization uses Utf8JsonWriter.WriteObjectValue<object>() to handle arbitrary values
  • Binary compatibility is fully maintained - existing client code continues to work without recompilation

Fixed Enum Members

Fixed enums (C# enum types) preserve their previously shipped members and values by comparing the current spec against the last contract. Because the underlying value of an integer-backed enum member is part of its public API, the generator keeps those values stable across regenerations, aligns member order to the last contract, and re-adds members that were dropped from the current spec.

[!NOTE]
These behaviors apply to integer-backed fixed enums (int/long).

Scenario: Explicit (Non-contiguous) Values Preserved

Description: When an integer enum's members carry explicit, non-contiguous values, the generator preserves each member's exact value from the last contract instead of reassigning positional ordinals.

Example:

Previous version (GA surface):

csharp
public enum CapacityReservationLevel
{
    OneHundred = 100,
    TwoHundred = 200,
    FiveHundred = 500,
}

Generated Result: The members keep their exact values (100, 200, 500) rather than being reassigned to 0, 1, 2.

Scenario: Removed Integer Enum Member Re-added

Description: When an integer enum member present in the last contract is dropped from the current spec, the generator re-adds it — at its original position and with its original explicit value — to avoid removing a previously shipped member.

Example:

Previous version:

csharp
public enum SampleEnum
{
    Alpha = 0,
    Beta = 1,
    Gamma = 2,
}

Current TypeSpec removes Beta:

csharp
public enum SampleEnum
{
    Alpha = 0,
    Gamma = 2,
}

Generated Result: Beta is re-added at its original position with its original value:

csharp
public enum SampleEnum
{
    Alpha = 0,
    Beta = 1,
    Gamma = 2,
}

Key Points:

  • Members shared between the current spec and the last contract are ordered to match the last contract and keep their last-contract values
  • New members introduced by the current spec are appended after the last-contract members, keeping their spec values
  • The re-added member's underlying value is read from the previously published assembly's metadata (no debug symbols required)

Scenario: Baseline-Accepted Removal Honored

Description: When the removal of an enum member has been intentionally accepted in the ApiCompat baseline — recorded as a MembersMustExist removal or an EnumValuesMustMatch value-difference suppression — the generator honors that decision and does not re-add the member.

Key Points:

  • Suppressed members are matched by the declaring enum's fully-qualified name and the member name
  • This lets a library intentionally drop a previously shipped enum member once the removal is reviewed and recorded in the baseline

Extensible Enum Members

Extensible enums (C# readonly partial struct types) preserve their previously shipped members by comparing the current spec against the last contract. The generator re-adds an extensible enum member that was dropped from the current spec, restoring it with its original wire value to avoid removing a previously shipped member.

Scenario: Removed Extensible Enum Member Re-added

Description: When an extensible enum member present in the last contract is dropped from the current spec, the generator restores it — as a public static property backed by its recovered const wire value — to keep the previously shipped API.

Example:

Previous version:

csharp
public readonly partial struct OperationStatusType : IEquatable<OperationStatusType>
{
    private const string CompletedValue = "Completed";
    private const string FailedValue = "Failed";
    private const string RunningValue = "Running";

    public static OperationStatusType Completed { get; } = new OperationStatusType(CompletedValue);
    public static OperationStatusType Failed { get; } = new OperationStatusType(FailedValue);
    public static OperationStatusType Running { get; } = new OperationStatusType(RunningValue);
    // ...
}

Current TypeSpec removes Running. Generated Result: Running is re-added (appended after the current members) with its original wire value:

csharp
public readonly partial struct OperationStatusType : IEquatable<OperationStatusType>
{
    private const string CompletedValue = "Completed";
    private const string FailedValue = "Failed";
    private const string RunningValue = "Running";

    public static OperationStatusType Completed { get; } = new OperationStatusType(CompletedValue);
    public static OperationStatusType Failed { get; } = new OperationStatusType(FailedValue);
    public static OperationStatusType Running { get; } = new OperationStatusType(RunningValue);
    // ...
}

Key Points:

  • Members that already exist in the current spec, are provided by custom code, or whose removal is accepted in the ApiCompat baseline are not re-added
  • Restored members are appended after the current spec's members, preserving the current spec's order

API Version Enum

Service version enums maintain backward compatibility by preserving version values from previous releases.

Scenario: API Version Removed or Changed

Description: When API versions are removed or changed in the TypeSpec, the generator preserves previous version enum members to prevent breaking existing code that references them.

Example:

Previous version had enum members:

csharp
public enum ServiceVersion
{
    V2021_10_01 = 1,
    V2022_01_01 = 2,
    V2022_06_01 = 3,
}

Current TypeSpec removes V2021_10_01 and adds V2023_01_01:

csharp
public enum ServiceVersion
{
    V2022_01_01 = 1,
    V2022_06_01 = 2,
    V2023_01_01 = 3,
}

Generated Result:

csharp
public enum ServiceVersion
{
    V2021_10_01 = 1,  // Preserved from previous version
    V2022_01_01 = 2,
    V2022_06_01 = 3,
    V2023_01_01 = 4,
}

Key Points:

  • Previous enum members are preserved even if removed from TypeSpec
  • Enum values are re-indexed to maintain sequential ordering
  • Version format and separator are detected from current versions and applied to previous versions

Non-abstract Base Models

Scenario: The previous version of a base model was defined as non-abstract.

Description: This can occur if the library was generated using a different generator that supported non-abstract base models. In such cases, the generator preserves the non-abstract nature of the base model to maintain compatibility.

Example:

Previous version generated using a different generator:

csharp
public class BaseModel
{
    public string CommonProperty { get; set; }
}

Current TypeSpec would generate:

csharp
public class BaseModel
{
    public string CommonProperty { get; set; }
}

Model Constructors

The generator maintains backward compatibility for model constructors on abstract base types to prevent breaking changes when constructor accessibility changes.

Scenario: Public Constructor on Abstract Base Type

Description: When an abstract base type had a public constructor in the previous version, but the current TypeSpec generation would create a private protected constructor, the generator automatically changes the modifier to public to maintain backward compatibility.

This commonly occurs when:

  • Migrating from autorest-generated code to TypeSpec-generated code
  • Abstract base types with discriminators had public parameterless constructors in previous versions

Example:

Previous version had a public parameterless constructor:

csharp
public abstract partial class SearchIndexerDataIdentity
{
    /// <summary> Initializes a new instance of SearchIndexerDataIdentity. </summary>
    public SearchIndexerDataIdentity()
    {
    }
}

Current TypeSpec would generate a private protected constructor:

csharp
public abstract partial class SearchIndexerDataIdentity
{
    /// <summary> Initializes a new instance of SearchIndexerDataIdentity. </summary>
    /// <param name="odataType"> A URI fragment specifying the type of identity. </param>
    private protected SearchIndexerDataIdentity(string odataType)
    {
        OdataType = odataType;
    }
}

Generated Compatibility Result:

When a matching public constructor exists in the last contract, the modifier is changed from private protected to public:

csharp
public abstract partial class SearchIndexerDataIdentity
{
    /// <summary> Initializes a new instance of SearchIndexerDataIdentity. </summary>
    /// <param name="odataType"> A URI fragment specifying the type of identity. </param>
    public SearchIndexerDataIdentity(string odataType)
    {
        OdataType = odataType;
    }
}

Key Points:

  • Only applies to abstract base types
  • The constructor must have matching parameters (same count, types, and names)
  • The modifier is changed from private protected to public
  • No additional constructors are generated; only the accessibility is adjusted

Scenario: Required Property Becomes Optional

Description: When a required model property becomes optional, the current initialization constructor no longer includes that property. To preserve source compatibility for callers that construct the model positionally, the generator restores the previously published public constructor as an overload. The restored overload chains to the closest current public constructor and assigns the now-optional property.

Example:

Previous version required both properties:

csharp
public partial class Widget
{
    public Widget(string name, string description)
    {
        Name = name;
        Description = description;
    }

    public string Name { get; }
    public string Description { get; }
}

Current TypeSpec makes description optional:

csharp
public partial class Widget
{
    public Widget(string name)
    {
        Name = name;
    }

    public string Name { get; }
    public string Description { get; set; }
}

Generated Compatibility Result:

csharp
public Widget(string name, string description) : this(name)
{
    Description = description;
}

Key Points:

  • The previous constructor must be public and no generated or custom constructor may already have the same parameters.
  • Every parameter removed from the current constructor must map to a public, settable property with the same type. Properties renamed through a code-generation customization are supported.
  • The current constructor used for chaining must have parameters that match an in-order subset of the previous constructor's parameters.
  • When no current public constructor is such an in-order subset, there is no safe chain target, so the previous constructor is not restored. A standalone constructor is not generated because it would bypass the current constructor's initialization (for example, the implicit base() call and inherited get-only properties).
  • If the constructor removal is accepted in an ApiCompat baseline, the generator does not restore it.

Scenario: Parameterless Constructor Becomes Parameterized

Description: When a model previously exposed an accessible parameterless constructor and a property later becomes required, generation replaces the parameterless constructor with one that accepts the required property. The generator restores the previous parameterless constructor and chains it to an appropriate current constructor with default values.

Example:

Previous version exposed a parameterless constructor:

csharp
public partial class Widget
{
    protected Widget()
    {
    }

    public string Name { get; set; }
}

Current TypeSpec makes name required:

csharp
public partial class Widget
{
    protected Widget(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

Generated Compatibility Result:

csharp
protected Widget() : this(default)
{
}

Key Points:

  • The previous parameterless constructor must be accessible and no accessible generated or custom parameterless constructor may already exist.
  • The restored constructor retains the previous accessibility.
  • The generator prefers an accessible current constructor with the fewest required parameters as the chain target. When necessary, it can chain to a private protected initialization constructor.
  • The generated parameterless mocking constructor is removed so it does not duplicate the restored constructor.
  • If the constructor removal is accepted in an ApiCompat baseline, the generator does not restore it.

Scenario: Constructor Parameter Name Restored by Signature Match

Description: When a constructor keeps the same parameter types (in the same order and count) but one or more parameters would be renamed — because of a @@clientName, a spec/property rename, a generator naming-rule change, or a casing correction — the new names would appear on the generated constructor. Renaming a constructor parameter is source-breaking for callers using named arguments and is reported by ApiCompat as a CP0017 parameter-name change, even though ordinary binary-compatibility checks may miss it. To avoid this, the generator matches the current constructor to the previously published one by signature and restores the previous parameter names.

This covers straight renames, casing corrections, and parameter swaps/rotations. For a swap or rotation (every previous name is still present, just in a different position) the generator realigns the existing parameter objects to the previous positional order instead of renaming positionally, so a caller's named argument stays bound to the same property.

Example:

Previous version published (name, vmSkuName):

csharp
public MockInputModel(string name, string vmSkuName)
{
    Name = name;
    VmSkuName = vmSkuName;
}

Current TypeSpec orders the properties differently, which would normally produce (vmSkuName, name). The generator restores the previous parameter order and names:

csharp
public MockInputModel(string name, string vmSkuName)
{
    VmSkuName = vmSkuName;
    Name = name;
}

Parameter Naming

The generator maintains backward compatibility for parameter names to ensure that existing code continues to compile when parameter names are corrected, standardized, or converted to follow naming conventions.

Scenario: Page Size Parameter Casing Correction

Description: When a paging parameter name has incorrect casing in the TypeSpec (e.g., maxpagesize instead of maxPageSize), the generator handles it in two ways:

  1. If the parameter exists in LastContractView: The generator uses the exact casing from the previous version to maintain backward compatibility
  2. If the parameter does NOT exist in LastContractView: The generator normalizes common badly-cased variants to proper camelCase (e.g., maxpagesize → maxPageSize)

This commonly occurs when:

  • TypeSpec defines a paging parameter with non-standard casing (e.g., all lowercase)
  • The generator needs to maintain API consistency while respecting the wire format
  • New paging operations need standardized parameter naming

Example:

Case 1: Parameter exists in LastContractView - badly-cased is preserved (backward compatibility)

Previous version had badly-cased parameter name:

csharp
public virtual AsyncPageable<Item> GetItemsAsync(int? maxpagesize = null, CancellationToken cancellationToken = default)
{
    HttpMessage CreateRequest()
    {
        var message = pipeline.CreateMessage();
        var request = message.Request;
        request.Method = RequestMethod.Get;
        var uri = new RequestUriBuilder();
        uri.Reset(endpoint);
        uri.AppendPath("/items", false);
        if (maxpagesize != null)
        {
            uri.AppendQuery("maxpagesize", maxpagesize.Value, true);  // Serialized name from spec
        }
        // ...
    }
    // ...
}

Current TypeSpec still defines parameter with bad casing:

typespec
@query maxpagesize?: int32;  // Lowercase in spec

Generated Compatibility Result:

The generator detects the parameter in LastContractView and preserves its exact badly-cased name to maintain backward compatibility:

csharp
public virtual AsyncPageable<Item> GetItemsAsync(int? maxpagesize = null, CancellationToken cancellationToken = default)
{
    HttpMessage CreateRequest()
    {
        var message = pipeline.CreateMessage();
        var request = message.Request;
        request.Method = RequestMethod.Get;
        var uri = new RequestUriBuilder();
        uri.Reset(endpoint);
        uri.AppendPath("/items", false);
        if (maxpagesize != null)
        {
            uri.AppendQuery("maxpagesize", maxpagesize.Value, true);  // Still badly-cased for backward compatibility
        }
        // ...
    }
    // ...
}

Case 2: Parameter does NOT exist in LastContractView - badly-cased is normalized

New paging operation with badly-cased parameter (no previous version):

typespec
@query maxpagesize?: int32;  // Lowercase in spec

Generated Result:

The generator normalizes the parameter name to proper camelCase since there's no previous version to maintain compatibility with:

csharp
public virtual AsyncPageable<Item> GetItemsAsync(int? maxPageSize = null, CancellationToken cancellationToken = default)
{
    HttpMessage CreateRequest()
    {
        var message = pipeline.CreateMessage();
        var request = message.Request;
        request.Method = RequestMethod.Get;
        var uri = new RequestUriBuilder();
        uri.Reset(endpoint);
        uri.AppendPath("/items", false);
        if (maxPageSize != null)
        {
            uri.AppendQuery("maxpagesize", maxPageSize.Value, true);  // Serialized name still uses spec's casing
        }
        // ...
    }
    // ...
}

Key Points:

  • Case 1 (Backward compatibility): If the parameter exists in LastContractView, its exact casing is preserved - even if badly-cased
  • Case 2 (Normalization): If the parameter does NOT exist in LastContractView, badly-cased variants are normalized to proper camelCase
  • The HTTP query parameter always uses the original serialized name from the spec (e.g., maxpagesize)
  • Existing client code continues to compile without changes

Scenario: Top Parameter Conversion to MaxCount

Description: For paging operations, the generator converts the top parameter to maxCount to follow standard naming conventions. However, backward compatibility is maintained in two ways:

  1. If the top parameter exists in LastContractView: The generator preserves the top parameter name (with its exact casing) to maintain backward compatibility
  2. If the top parameter does NOT exist in LastContractView: The generator converts top to the standardized maxCount parameter name

This commonly occurs when:

  • Migrating from an older API version or generator that used top for pagination limits
  • TypeSpec defines paging operations with a top parameter
  • The generator needs to standardize on maxCount while maintaining backward compatibility

Example:

Case 1: Top parameter exists in LastContractView - preserved for backward compatibility

Previous version had top parameter:

csharp
public virtual AsyncPageable<Item> GetItemsAsync(int? top = null, CancellationToken cancellationToken = default)
{
    HttpMessage CreateRequest()
    {
        var message = pipeline.CreateMessage();
        var request = message.Request;
        request.Method = RequestMethod.Get;
        var uri = new RequestUriBuilder();
        uri.Reset(endpoint);
        uri.AppendPath("/items", false);
        if (top != null)
        {
            uri.AppendQuery("top", top.Value, true);  // Serialized name from spec
        }
        // ...
    }
    // ...
}

Current TypeSpec still defines top parameter:

typespec
op getItems(@query top?: int32): Page<Item>;

Generated Compatibility Result:

The generator detects the top parameter in LastContractView and preserves it exactly to maintain backward compatibility:

csharp
public virtual AsyncPageable<Item> GetItemsAsync(int? top = null, CancellationToken cancellationToken = default)
{
    HttpMessage CreateRequest()
    {
        var message = pipeline.CreateMessage();
        var request = message.Request;
        request.Method = RequestMethod.Get;
        var uri = new RequestUriBuilder();
        uri.Reset(endpoint);
        uri.AppendPath("/items", false);
        if (top != null)
        {
            uri.AppendQuery("top", top.Value, true);  // Still uses "top" for backward compatibility
        }
        // ...
    }
    // ...
}

Case 2: Top parameter does NOT exist in LastContractView - converted to maxCount

New paging operation with top parameter (no previous version):

typespec
op getItems(@query top?: int32): Page<Item>;

Generated Result:

The generator converts the parameter name to standardized maxCount since there's no previous version to maintain compatibility with:

csharp
public virtual AsyncPageable<Item> GetItemsAsync(int? maxCount = null, CancellationToken cancellationToken = default)
{
    HttpMessage CreateRequest()
    {
        var message = pipeline.CreateMessage();
        var request = message.Request;
        request.Method = RequestMethod.Get;
        var uri = new RequestUriBuilder();
        uri.Reset(endpoint);
        uri.AppendPath("/items", false);
        if (maxCount != null)
        {
            uri.AppendQuery("top", maxCount.Value, true);  // Serialized name still uses "top" from spec
        }
        // ...
    }
    // ...
}

Key Points:

  • Case 1 (Backward compatibility): If top parameter exists in LastContractView, its exact name and casing are preserved
  • Case 2 (Standardization): If top parameter does NOT exist in LastContractView, it is converted to maxCount for consistency
  • The HTTP query parameter always uses the original serialized name from the spec (e.g., top)
  • This conversion is specific to paging operations only
  • Existing client code with top continues to compile without changes
  • New code benefits from the standardized maxCount naming convention

Scenario: Method Parameter Name Preserved from Last Contract

Description: When a service method parameter is renamed by the spec or by the generator (e.g., a @@clientName, a TypeSpec property rename, or a generator naming-rule change), the new name would normally appear on the generated convenience and protocol methods. Renaming a parameter is source-breaking for callers using named arguments and is not flagged by ApiCompat / binary-compat tooling. To avoid this, the generator looks up the parameter's original (spec) name in LastContractView and, when a previously-published parameter with that name exists on the matching method (matched by method name, allowing for the sync/async pair), restores the previously-published parameter name on the current method.

This generalizes the paging-specific top → maxCount and page-size casing scenarios above so that any renamed parameter on any operation falls back to the prior published name.

Example:

Previous version exposed oldParam on GetSomething:

csharp
public virtual ClientResult GetSomething(string oldParam, RequestOptions options = null)
{
    // ...
}

Current TypeSpec renames the parameter to newParam (e.g., via @@clientName or a property rename), which would normally produce:

csharp
public virtual ClientResult GetSomething(string newParam, RequestOptions options = null)
{
    // ...
}

Generated Compatibility Result:

The generator detects oldParam on the matching method in LastContractView and restores that name on the current method:

csharp
public virtual ClientResult GetSomething(string oldParam, RequestOptions options = null)
{
    // body and HTTP request still use the spec's serialized name; only the public parameter name is preserved
}

Key Points:

  • Lookup is scoped to the matching service method (allowing for the sync/async pair) so a parameter name shared across multiple methods cannot false-match another method's parameter
  • The HTTP query/path/header/body serialized name continues to use the spec's wire name — only the C# parameter identifier is restored
  • Existing source code using named arguments (e.g., client.GetSomething(oldParam: "x")) continues to compile
  • If no matching parameter is found in LastContractView, the generator uses the current (renamed) name

Content-Type Parameter Ordering

The generator places the contentType parameter after the body (content) parameter in method signatures. However, backward compatibility is maintained when the last contract had a different ordering.

Scenario: Content-Type Before Body Preserved from Last Contract

Description: The generator places contentType after the content (body) parameter. However, if the last contract had contentType before content, the generator preserves that ordering to avoid breaking existing code.

This commonly occurs when a library was previously generated with contentType before body and has already been released (GA'd).

Example:

contentType before body exists in LastContractView - preserved for backward compatibility

Previous version had contentType before content:

csharp
public virtual ClientResult UpdateSkillDefaultVersion(string skillId, string contentType, BinaryContent content, RequestOptions options = null)
{
    // ...
}

Current TypeSpec defines a content type:

typespec
op UpdateSkillDefaultVersion(
  @path skill_id: string,
  @header contentType: string,
  @body body: SetDefaultSkillVersionBody,
): SkillResource;

Generated Compatibility Result:

The generator detects that the previous contract had contentType before content and preserves that ordering:

csharp
public virtual ClientResult UpdateSkillDefaultVersion(string skillId, string contentType, BinaryContent content, RequestOptions options = null)
{
    // contentType stays before content for backward compatibility
}

Model Base Types

The generator can preserve a previously shipped model base when the current TypeSpec hierarchy no longer contains it. Automatic restoration is intentionally limited to compatible generated root models and mapped SystemObjectModelProvider bases. For ordinary generated bases, both the derived model and displaced current base must be memberless. Mapped restoration requires a current input base, which remains the authoritative wire contract while the shipped CLR property and constructor surface is restored.

When no current provider represents the shipped CLR base, a downstream generator can override TypeFactory.CreateLastContractModelBaseCore to supply a mapping for its known inheritable framework types. The returned type is still subject to the standard mapped-base compatibility checks and remains local to the model being restored. A mapped base is not reconstructed when the current model has become a root because there is no separate current-base contract to reconcile safely.

The current base is retained when broader reconciliation would be required, including a conflicting custom base declaration, structs, polymorphic or derived hierarchies, arbitrary referenced types, and constructed generic bases. Unrelated custom members are permitted only for mapped-base restoration; generated-base restoration with custom code remains unsupported.

A derived generator can override ModelProvider.RequiresParameterlessBaseConstructor to require a mapped constructor callable without arguments before the restored hierarchy is committed. ClientModel enables this requirement for JSON/XML and multipart-form-data models, because their serialization partials can emit an empty constructor with an implicit base() call. The constructor must be public or protected and pass the existing overload and attribute checks. This check does not apply to core providers or ClientModel models without JSON/XML or multipart-form-data serialization; it does not synthesize constructor arguments.

Restoration checks the shipped property/accessor and method contracts, including nullable value types, parameter names, ref modifiers, params, optional defaults, and interface assignability. The default mapped input/property check requires matching value-type nullability; intentional downstream shape changes remain explicit compatibility-hook overrides. A selected historical constructor must also match its bound properties' value-type nullability. When a current property visitor removes a property, symbol-backed restoration does not reintroduce its historical declaration and the one-to-one checks reject restoration. Both full and initialization constructor calls must be provably callable on the mapped target; ambiguous overloads and changed omitted defaults are rejected. Generated-base constructors must remain property-backed. Historical generic methods, constants, by-ref returns, indexers, required-member properties, and defaults that cannot be represented losslessly are rejected rather than reconciled. Public or protected historical nested type declarations are also rejected for generated-base and symbol-backed mapped-base restoration; nested type emission and CLR nested-type reconciliation are not supported. These checks do not rediscover the complete CLR surface of an ordinary downstream mapping whose input omits members.

Client Methods

Scenario: Client Method Parameter Ordering Changed

Description: When only the parameter ordering changes in a client method, the generator restores the order from LastContractView on the current method instead of adding a compatibility overload. This preserves existing positional calls to both protocol and convenience methods, including their synchronous and asynchronous variants. A previous contract must be available; without it, the generator uses the current parameter order.

Example:

Previous version of the client:

csharp
public virtual ClientResult GetData(BinaryContent content, int count, bool includeDetails, RequestOptions options = null);
public virtual ClientResult<string> GetData(string data, int count, bool includeDetails, CancellationToken cancellationToken = default);

Current TypeSpec moves includeDetails before count, which would normally change the order of those parameters in the generated methods:

typespec
op getData(@body data: string, @header includeDetails: boolean, @query count: int32): string;

Generated Compatibility Result:

The generator keeps the previously published order on both methods:

csharp
public virtual ClientResult GetData(BinaryContent content, int count, bool includeDetails, RequestOptions options = null);
public virtual ClientResult<string> GetData(string data, int count, bool includeDetails, CancellationToken cancellationToken = default);

The corresponding GetDataAsync methods likewise preserve their own last-contract parameter order.

Key Points:

  • The method name and return type must match the previous contract, with the same parameter count and parameters matched by normalized name and type. Adding or removing parameters or changing their types is not part of this scenario.
  • Protocol and convenience methods are matched independently, so their parameter orders can be restored separately or together.
  • When a protocol method is reordered, convenience method calls to it are updated to pass arguments in the restored order.
  • Parameter XML documentation is reordered to match the restored signature.

Scenario: New Optional Non-Body Parameter Added to a Service Method

Description: When the current TypeSpec adds one or more new optional non-body parameters (e.g. query, header, path) to an existing service method, the generator emits a hidden back-compat overload that matches the previous contract's signature and delegates to the new method, passing default for the new parameter(s). The behavior is intentionally restricted to non-body parameters because adding a body parameter typically reflects a schema change and is handled differently.

Rules:

  • The previous method's parameters must appear, in the same relative order and matching by name and type, within the current method's parameters.
  • Every "extra" parameter on the current method must be optional (i.e. have a default value).
  • No "extra" parameter on the current method may be a body parameter; if any extra parameter is a body parameter, no back-compat overload is added.
  • The back-compat overload is hidden via [EditorBrowsable(EditorBrowsableState.Never)] and has all default values stripped from its parameters to avoid ambiguous call sites.

Example:

Previous version of the client:

csharp
public virtual ClientResult GetData(int p1, BinaryContent body, RequestOptions options = null);
public virtual Task<ClientResult> GetDataAsync(int p1, BinaryContent body, RequestOptions options = null);

Current TypeSpec adds an optional query parameter p2:

typespec
op getData(@query p1: int32, @body body: SampleModel, @query p2?: boolean): string;

Generated Client:

The generated client includes the current methods (with the new optional p2 parameter) and the hidden back-compat overloads that match the previous contract's signature. Required parameters come first, followed by optional parameters, with RequestOptions last — matching the Azure SDK parameter ordering guidelines.

csharp
// Current sync method generated from the updated TypeSpec.
public virtual ClientResult GetData(int p1, BinaryContent body, bool? p2 = default, RequestOptions options = null)
{
    // ... implementation ...
}

// Current async method generated from the updated TypeSpec.
public virtual async Task<ClientResult> GetDataAsync(int p1, BinaryContent body, bool? p2 = default, RequestOptions options = null)
{
    // ... implementation ...
}

// Back-compat sync overload matching the previous contract's signature.
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual ClientResult GetData(int p1, BinaryContent body, RequestOptions options)
{
    return this.GetData(p1: p1, body: body, p2: default, options: options);
}

// Back-compat async overload matching the previous contract's signature.
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual Task<ClientResult> GetDataAsync(int p1, BinaryContent body, RequestOptions options)
{
    return this.GetDataAsync(p1: p1, body: body, p2: default, options: options);
}

The back-compat overloads are hidden from IntelliSense via [EditorBrowsable(EditorBrowsableState.Never)], have all default values stripped to avoid ambiguous call sites with the current methods, and delegate to the current method passing default for each new parameter.

Scenario: Value-Type Parameter Nullability Removed

Description: When a value-type parameter that was nullable in the last contract (T?) becomes non-nullable (T) in the current generation, the generator emits a hidden back-compat overload that reproduces the previous nullable signature and delegates to the current method, unwrapping the argument with .Value. The forwarded argument is guarded with Argument.AssertNotNull so a null argument produces a clear ArgumentNullException rather than an InvalidOperationException from .Value. This also covers extensible enums (which are read back from the last contract as structs), matched by fully-qualified type name.

Example:

Previous contract:

csharp
public string GetData(string data, FileFormatType? value = default, bool? flag = default);

Current generation makes value non-nullable and required:

csharp
public string GetData(string data, FileFormatType value, bool? flag = default);

Generated Compatibility Overload:

csharp
[EditorBrowsable(EditorBrowsableState.Never)]
public string GetData(string data, FileFormatType? value = default, bool? flag = default)
{
    Argument.AssertNotNull(value, nameof(value));
    return this.GetData(data: data, value: value.Value, flag: flag);
}

Key Points:

  • Only applies to value types (including extensible enums); reference-type nullability (string? vs string) cannot form distinct overloads and is not handled.
  • Only the T? → T direction (removing nullability) is handled, not the widening direction.
  • When the current parameter is required, the previous optional default is preserved so callers that omitted it still compile; the AssertNotNull guard turns a null argument into a clear ArgumentNullException.
  • A ref/out parameter is not eligible (its value cannot be forwarded through .Value), so no overload is generated in that case.
  • The overload is hidden via [EditorBrowsable(EditorBrowsableState.Never)].

Scenario: Nullable Optional Parameter Became Required

Description: When a nullable parameter that was optional in the last contract (had a default value) becomes required in the current generation (same type, but no longer has a default), callers that omitted it no longer compile. The generator emits a hidden reduced-arity overload that drops the now-required parameter and delegates to the current method, supplying the parameter's previous default value.

Example:

Previous contract:

csharp
public string GetData(string data, FileFormatType? value = default, bool? flag = default);

Current generation keeps value nullable but makes it required:

csharp
public string GetData(string data, FileFormatType? value, bool? flag = default);

Generated Compatibility Overload:

csharp
[EditorBrowsable(EditorBrowsableState.Never)]
public string GetData(string data, bool? flag = default)
{
    return this.GetData(data: data, value: default, flag: flag);
}

Key Points:

  • Applies only to a single nullable parameter that changed from optional to required; the parameter's type is otherwise unchanged.
  • The dropped parameter's type must differ from every other parameter's type so the reduced-arity overload's call sites stay unambiguous.
  • The optionality of the remaining parameters is preserved, and the overload is hidden via [EditorBrowsable(EditorBrowsableState.Never)].
  • Unlike the nullability-removal scenario, the delegation passes the previous default — there is no .Value unwrap and no runtime throw.

Packages Http Client Csharp Generator Docs Spector (`packages/http-client-csharp/generator/docs/spector.md`)

By default all tests under the http-specs folder will be generated. In order to get coverage for a test within one of the specs you must write a test case that executes the operation.

Generating Spector libraries

Each spec will be generated when running ./eng/scripts/Generate.ps1 within the same folder structure it exists in the http-specs package. As an example http/authentication/api-key
will live in the following folder inside the Spector test projects

The files that get generated here will only be stubbed public APIs. This is done to minimize the size of the repo and reduce PR diff noise when an internal implementation is modified which can potentially effect every model. Seeing the same diff in hundreds of files doesn't provide value it only introduces noise.

If you want to manually generate the non stubbed version you can call Generate.ps1 with the name of the project you want to generate and set Stubbed to false.
For example if you want to generate the non stubbed version of http/authentication/api-key you can do the following.

powershell
./eng/scripts/Generate.ps1 http/authentication/api-key -Stubbed $false

Writing Spector tests

Generating the stubs allows us write tests against the public API surface that will compile. To do this we add a test class in same folder structure although this time we will modify the casing slightly to match dotnet standards.

In this ApiKeyTests.cs we can write tests against the generated stubs like the following.

c#
[SpectorTest]
public Task Valid() => Test(async (host) =>
{
    ClientResult response = await new ApiKeyClient(host, new ApiKeyCredential("valid-key"), null).ValidAsync();
    Assert.AreEqual(204, response.GetRawResponse().Status);
});

This test validates that we can successfully call the mock Spector service and verifies that we receive a successful response with code 204. Notice that we are using SpectorTest attribute instead of the standard NUnit Test attribute. This is because the test will not complete successfully against the stub and we must generate the full library before running the test.

The SpectorTest attribute will dynamically determine if the library is stubbed and mark the step as Ignore if it is. If it is not stubbed it will run the full test.

Testing Spector scenarios

All of this is automated into a script called ./eng/scripts/Test-Spector.ps1. This script will find all generated Spector projects and regenerate each of them without using the StubLibraryGenerator. It will then run dotnet test which will cause tests using the SpectorTest attribute to no longer be skipped. Finally it will restore the files back to the stubs if everything was successful and if not it will leave the files in place so you can investigate.

Here is an example output for one library

text
/* Detailed source-code truncated for AI context efficiency. */

Debugging generation of a Spector library

If you want to debug the generation of one of the Spector libraries you can do this with the StubLibraryGenerator. There are launch settings for each Spector test which are already configured to use this generator.

The generator does not skip generating the methods bodies and xml docs it simply removes them before saving the files to disk. Therefore you can break at any point and debug any part of the full generation without needing to constantly flip the generator being used back and forth.

Debugging Spector tests

To debug one of the SpectorTest you will need to generate the non stubbed library first by calling Generate.ps1 with Stubbed set to false.

powershell
./eng/scripts/Generate.ps1 http/authentication/api-key -Stubbed $false

If you don't do this the test will be ignored by SpectorTest attribute since you cannot run a test when the library has no implementation.

Problematic specs

The ./eng/scripts/Generate.ps1 script allows you to exclude a problematic spec by adding it to the $failingSpecs list. Ideally no specs should be here, but if we need to we can temporarily add items to the list and create tracking issues in github to remove them at a later time.


Packages Http Client Java .Tspd Docs Usage (`packages/http-client-java/.tspd/docs/usage.md`)

Prerequisite

Install Node.js 20 or above. (Verify by running node --version)

Install Java 17 or above. (Verify by running java --version)

Install Maven. (Verify by running mvn --version)


Functionality and generated code

The emitter generates a few major components:

Router

The highest-level component that your code interacts with directly is the router implementation.
@typespec/http-server-js generates a static router that you can bind to an implementation of an HTTP server.

The router is generated in the http/router.js module within the output directory. Each service will have its own
router implementation named after the service. For example, given a service namespace named Todo, the router module
will export a function createTodoRouter. This function creates an instance of a router that dispatches methods within
the Todo service.

ts
import { createTodoRouter } from "../tsp-output/@typespec/http-server-js/http/router.js";

const router = createTodoRouter(users, todoItems, attachments);

As arguments, the createTodoRouter function expects implementations of the underlying service interfaces. These
interfaces are explained further in the next section.

Once the router is created, it is bound to an instance of the HTTP server. The router's dispatch method implements the
Node.js event handler signature for the request event on a Node.js HTTP server.

ts
const server = http.createServer();

server.on("request", router.dispatch);

server.listen(8080, () => {
  console.log("Server listening on http://localhost:8080");
});

Alternatively, the router can be used with Express.js instead of the Node.js HTTP server directly. If the express
feature is enabled in the emitter options, the router will expose an expressMiddleware property that implements the
Express.js middleware interface.

ts
import express from "express";

const app = express();

app.use(router.expressMiddleware);

app.listen(8080, () => {
  console.log("Server listening on http://localhost:8080");
});

Service interfaces

The emitter generates interfaces for each collection of service methods that exists in the service namespace.
Implementations of these interfaces are required to instantiate the router. When the router processes an HTTP request,
it will call the appropriate method on the service implementation after determining the route and method.

For example, given the following TypeSpec namespace Users within the Todo service:

tsp
namespace Users {
  @route("/users")
  @post
  op create(user: User): WithStandardErrors<
    | UserCreatedResponse
    | UserExistsResponse
    | InvalidUserResponse>;
}

The emitter will generate a corresponding interface Users within the module models/all/todo/index.js in the output
directory.

ts
/** An interface representing the operations defined in the 'Todo.Users' namespace. */
export interface Users<Context = unknown> {
  create(
    ctx: Context,
    user: User,
  ): Promise<
    | UserCreatedResponse
    | UserExistsResponse
    | InvalidUserResponse
    | Standard4XxResponse
    | Standard5XxResponse
  >;
}

An object implementing this Users interface must be passed to the router when it is created. The Context type
parameter represents the underlying protocol or framework-specific context that the service implementation may inspect.
If you need to access the HTTP request or response objects directly in the implementation of the service methods, you
must use the HttpContext type as the Context argument when implementing the service interface. Otherwise, it is safe
to use the default unknown argument.

ts
import { HttpContext } from "../tsp-output/@typespec/http-server-js/helpers/router.js";
import { Users } from "../tsp-output/@typespec/http-server-js/models/all/todo/index.js";

export const users: Users<HttpContext> = {
  async create(ctx, user) {
    // Implementation
  },
};

Models

The emitter generates TypeScript interfaces that represent the model types used in the service operations. This allows
the service implementation to interact with the data structures carried over the HTTP protocol in a type-safe manner.

Operation functions

While your code should never need to interact with these functions directly, the emitter generates a function per HTTP
operation that handles the parsing and validation of the request contents. This allows the service implementation to be
written in terms of ordinary TypeScript types and values rather than raw HTTP request and response objects. In general:

  • The Node.js HTTP server or Express.js application (your code) calls the router (generated code), which determines
    which service operation function (generated code) to call based on the route, method, and other HTTP metadata in the
    case of shared routes.
  • The operation function (generated code) deserializes the request body, query parameters, and headers into TypeScript
    types, and may perform request validation.
  • The operation function (generated code) calls the service implementation (your code) with the deserialized request
    data.
  • The service implementation (your code) returns a result or throws an error.
  • The operation function (generated code) responds to the HTTP request on your behalf, converting the result or error
    into HTTP response data.

Packages Http Server Js .Tspd Docs Header (`packages/http-server-js/.tspd/docs/header.md`)

:warning: This package is highly experimental and may be subject to breaking changes and bugs. Please expect that your code may need to be updated as this package evolves, and please report any issues you encounter.


Packages Json Schema .Tspd Docs Usage (`packages/json-schema/.tspd/docs/usage.md`)

Add the @jsonSchema decorator to any types or namespaces you want to emit as JSON Schema.

typespec
import "@typespec/json-schema";

using JsonSchema;

@jsonSchema
namespace Example;

model Car {
  make: string;
  modelName: string;
}

Packages Versioning .Tspd Docs Usage (`packages/versioning/.tspd/docs/usage.md`)

Consuming versioning library from an emitter

Get the service representation at a given version

Versioning library works with projection to project the service at a given version.

ts
// Get a list of all the different version of the service and the projections
const projections = buildVersionProjections(program, serviceNamespace);

for (const projection of projections) {
  const projectedProgram = projectProgram(program, projection.projections);
  // projectedProgram now contains the representation of the service at the given version.
}

Get list of versions and version dependency across namespaces

Versioning library works with projection to project the service at a given version.

ts
const versions = resolveVersions(program, serviceNamespace);
// versions now contain a list of all the version of the service namespace and what version should all the other dependencies namespace use.

Consume versioning manually

If the emitter needs to have the whole picture of the service evolution across the version then using the decorator accessor will provide the metadata for each type:

  • getAddedOn
  • getRemovedOn
  • getRenamedFromVersion
  • getMadeOptionalOn

Samples Client Csharp SampleService Package.Json (`docs/samples/client/csharp/SampleService/package.json`)

{
"dependencies": {
"@typespec/http-client-csharp": "1.0.0-alpha.20260310.2",
"logging-plugin": "file:../plugins/logging"
}
}


Samples Client Csharp SampleService Tspconfig.Yaml (`docs/samples/client/csharp/SampleService/tspconfig.yaml`)

emit:

  • "@typespec/http-client-csharp"
    options:
    "@typespec/http-client-csharp":
    emitter-output-dir: "{project-root}/SampleClient"

Packages Http Client Csharp Eng Scripts Docs RegenPreview (`packages/http-client-csharp/eng/scripts/docs/RegenPreview.md`)

RegenPreview.ps1

Overview

RegenPreview.ps1 is a PowerShell script that automates the process of building local generator packages and regenerating libraries for validation purposes. This script supports three modes:

  • Azure SDK Mode: Regenerate Azure SDK for .NET libraries
  • OpenAI Mode: Regenerate the OpenAI .NET library
  • Spector Mode: Regenerate Azure spector test scenarios in azure-sdk-for-net

This script is designed to streamline the workflow for testing changes to the TypeSpec HTTP Client C# generator before submitting a pull request.

Purpose

When making changes to the TypeSpec HTTP Client C# generator, it may be helpful to validate what the effects will be to generated libraries. This script automates the entire validation workflow by:

Azure SDK Mode

  1. Building local versions of the generator packages
  2. Updating the Azure SDK for .NET repository to use these local packages
  3. Regenerating all libraries (or specific libraries if -Select or -Libraries is specified)
  4. Cleaning up all modifications after validation

OpenAI Mode

  1. Building local version of the unbranded generator package
  2. Building local NuGet framework packages
  3. Updating the OpenAI .NET repository's codegen configuration
  4. Regenerating the OpenAI library using Invoke-CodeGen.ps1
  5. Cleaning up all modifications after validation

Spector Mode

  1. Building local version of the unbranded generator package
  2. Building local NuGet framework packages
  3. Wiring the Azure generator in azure-sdk-for-net to use local packages
  4. Running the Azure generator's Generate.ps1 to regenerate spector test scenarios
  5. Cleaning up all modifications after validation

Prerequisites

  • PowerShell 7.0 or later (cross-platform: Windows, Linux, macOS)
  • Node.js and npm installed
  • .NET SDK installed
  • Local clone of the typespec repository (unbranded generator)
  • Local clone of the azure-sdk-for-net repository (for Azure SDK mode)
  • Local clone of the openai-dotnet repository (for OpenAI mode)

Usage

Azure SDK Mode - Basic Usage (Regenerate All)

Windows:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\path\to\azure-sdk-for-net"

Linux/macOS:

powershell
./RegenPreview.ps1 -SdkLibraryRepoPath "/home/user/repos/azure-sdk-for-net"

This will:

  • Clean and build the unbranded and Azure generators with local changes
  • Regenerate all libraries automatically
  • Restore all modified metadata files on success (ie. package.json, package-lock.json)

OpenAI Mode - Basic Usage

Windows:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\path\to\openai-dotnet"

Linux/macOS:

powershell
./RegenPreview.ps1 -SdkLibraryRepoPath "/home/user/repos/openai-dotnet"

This will:

  • Clean and build the unbranded generator with local changes
  • Build and package NuGet framework packages
  • Update root nuget.config with local NuGet source and packageSourceMapping
  • Update codegen/package.json with local generator
  • Regenerate codegen/package-lock.json with npm install
  • Update OpenAI.Library.Plugin.csproj with local NuGet packages
  • Regenerate the OpenAI library
  • Restore all modified generator metadata files on success

Note: OpenAI mode is automatically detected when the repository path contains "openai-dotnet". The -Select, -Azure, -Unbranded, -Mgmt, and -Libraries parameters are not applicable in OpenAI mode.

Parameters

`-SdkLibraryRepoPath` (Required)

The local file system path to your SDK repository (azure-sdk-for-net or openai-dotnet). When the path contains "openai-dotnet", the script automatically operates in OpenAI mode.

Windows Example:

powershell
-SdkLibraryRepoPath "C:\repos\azure-sdk-for-net"

Linux/macOS Example:

powershell
-SdkLibraryRepoPath "/home/user/repos/azure-sdk-for-net"

`-Select` (Optional)

Azure SDK Mode only. When specified, displays an interactive menu to select specific libraries to regenerate. If omitted, all libraries are regenerated automatically.

Can be combined with generator filter parameters (-Azure, -Unbranded, -Mgmt) to interactively select from a filtered subset.

Not applicable in OpenAI mode.

Example:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Select

`-Azure` (Optional)

Azure SDK Mode only. When specified, only regenerates libraries using the Azure-branded generator (@azure-typespec/http-client-csharp).

Not applicable in OpenAI mode.

Example:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Azure

`-Unbranded` (Optional)

Azure SDK Mode only. When specified, only regenerates libraries using the unbranded generator (@typespec/http-client-csharp).
Not applicable in OpenAI mode.

Example:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Unbranded

`-Mgmt` (Optional)

Azure SDK Mode only. When specified, only regenerates libraries using the management plane generator (@azure-typespec/http-client-csharp-mgmt).

If no generator filter parameter is specified, all libraries are regenerated by default.

Not applicable in OpenAI mode.

Example:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Mgmt

`-Libraries` (Optional)

Azure SDK Mode only. A comma-separated list of library names to regenerate. This parameter can be combined with -Select and generator filters (-Azure, -Unbranded, -Mgmt), but not with -Spector.

Not applicable in OpenAI mode.

Example:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Libraries "Azure.ResourceManager.AppContainers, Azure.Data.AppConfig"

`-Spector` (Optional)

Azure SDK Mode only. When specified, regenerates the Azure spector test scenarios in azure-sdk-for-net instead of regenerating SDK libraries. This builds the local unbranded generator, wires it into the Azure generator, and runs the Azure generator's Generate.ps1 to regenerate spector test projects.

Mutually exclusive with -Select, -Azure, -Unbranded, -Mgmt, and -Libraries.

Not applicable in OpenAI mode.

Example:

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Spector

Interactive Selection

When running with the -Select flag, the script presents an interactive menu listing all available libraries:

text
==================== LIBRARY SELECTION ====================
Found 28 libraries available for regeneration

Azure-branded libraries (@azure-typespec/http-client-csharp):
  [ 1] Azure.AI.VoiceLive                                  (ai)
  [ 2] Azure.Data.AppConfiguration                         (appconfiguration)
  ...

Unbranded libraries (@typespec/http-client-csharp):
  [12] Azure.AI.Projects                                   (ai)
  [13] Azure.AI.OpenAI                                     (openai)

Management plane libraries (@azure-typespec/http-client-csharp-mgmt):
  [14] Azure.ResourceManager.AgriculturePlatform           (agricultureplatform)
  [15] Azure.ResourceManager.ArizeAIObservabilityEval      (arizeaiobservabilityeval)
  ...

Enter library numbers to regenerate (comma-separated), 'all' for all libraries, or 'q' to quit:
Example: 1,3,5  or  1-4,7  or  all

Selection Options:

  • Single library: 1
  • Multiple libraries: 1,3,5
  • Range of libraries: 1-4
  • Combination: 1-4,7,10
  • All libraries: all
  • Quit: q

How It Works

The script operates in two distinct modes based on the repository path provided.

Azure SDK Mode

The script performs the following steps in sequence when the repository path points to azure-sdk-for-net:

Step 0: Library Selection (Interactive Mode Only)

  • Parses Library_Inventory.md in the azure-sdk-for-net repository
  • Applies any generator filters if specified (-Azure, -Unbranded, -Mgmt)
  • Displays available libraries grouped by generator type
  • Prompts user to select which libraries to regenerate
  • Skipped when -Select is not specified (non-interactive mode)

Step 1: Build Unbranded Generator

  • Runs npm ci to install dependencies
  • Runs npm run clean to ensure a clean build
  • Runs npm run build to build @typespec/http-client-csharp

Step 2: Package Unbranded Generator

  • Generates a local version string: 1.0.0-alpha.YYYYMMDD.hash
  • Updates package.json with the local version
  • Runs npm pack to create a .tgz package
  • Moves the package to the debug folder
  • Restores original package.json

Step 2.5: Build and Package NuGet Packages

  • Generates NuGet packages for three generator framework projects:
    • Microsoft.TypeSpec.Generator
    • Microsoft.TypeSpec.Generator.Input
    • Microsoft.TypeSpec.Generator.ClientModel
  • Uses Debug configuration with --no-build (reuses binaries from Step 1)
  • Outputs .nupkg files to the debug folder
  • In Azure SDK mode: Updates Directory.Generation.Packages.props and NuGet.Config
  • In OpenAI mode: Proceeds to Step 3 (OpenAI regeneration)

OpenAI Mode

When the repository path contains "openai-dotnet", the script switches to OpenAI mode and performs the following steps after Step 2.5:

Step 3: Update OpenAI Generator and Regenerate

What the script does:

  • Updates nuget.config (at the root of the repo) with local NuGet package source
    • Adds the local debug folder as a package source
    • Updates packageSourceMapping to allow Generator packages from the local source (required for OpenAI's package source mapping configuration)
  • Updates codegen/package.json with local unbranded generator dependency (@typespec/http-client-csharp)
    • Checks both dependencies and devDependencies sections for the package
    • Updates whichever section(s) contain the package to use file: protocol pointing to local .tgz
  • Deletes codegen/package-lock.json to force regeneration with new dependencies
  • Runs npm install in the codegen directory to regenerate the lock file with local dependencies
  • Updates codegen/generator/src/OpenAI.Library.Plugin.csproj with local NuGet package version for Microsoft.TypeSpec.Generator.ClientModel
  • Invokes scripts/Invoke-CodeGen.ps1 -Clean to regenerate the OpenAI library
  • On successful regeneration, restores all modified artifacts:
    • codegen/package.json
    • codegen/package-lock.json
    • nuget.config
    • codegen/generator/src/OpenAI.Library.Plugin.csproj
  • On failure, leaves all modified artifacts in place for debugging
  • Displays success message and exits

Note: Filter parameters (-Azure, -Unbranded, -Mgmt, -Select, -Libraries) are not applicable in OpenAI mode and will cause an error if specified.


Azure SDK Mode (continued)

In Azure SDK mode, the script continues with additional steps after Step 2.5:

Step 3: Update and Build Azure Generator

  • Updates the Azure generator's package.json to reference the local unbranded package using file: protocol
  • Runs npm run clean to clear previous builds
  • Runs npm install --package-lock-only to update the lock file with the new local dependency
  • Runs npm ci for clean installation based on the updated lock file
  • Builds the Azure generator with npm run build

Step 4: Package Azure Generator

  • Updates package.json with the local version (dependency already set in Step 3)
  • Runs npm pack to create a .tgz package
  • Moves the package to the debug folder
  • Restores original package.json (after both Steps 3 and 4 complete)

Step 5: Update eng Folder Artifacts

  • Updates package.json artifacts in azure-sdk-for-net/eng:
    • azure-typespec-http-client-csharp-emitter-package.json
    • azure-typespec-http-client-csharp-emitter-package-lock.json
    • http-client-csharp-emitter-package.json
    • http-client-csharp-emitter-package-lock.json
  • Uses a temporary directory to safely update the lock files
  • These files control which generator versions are used during library regeneration

Step 6: Update and Build Management Plane Generator

  • Updates the management plane generator (@azure-typespec/http-client-csharp-mgmt) located in azure-sdk-for-net/eng/packages/http-client-csharp-mgmt
  • Updates package.json to reference both:
    • Local Azure generator (@azure-typespec/http-client-csharp) using file: protocol
    • Local unbranded generator (@typespec/http-client-csharp) using file: protocol
  • Runs npm install to install dependencies
  • Runs npm run clean to ensure a clean build
  • Runs npm run build to build the management plane generator
  • Creates a local package with npm pack
  • Updates eng folder emitter package artifacts:
    • azure-typespec-http-client-csharp-mgmt-emitter-package.json
    • azure-typespec-http-client-csharp-mgmt-emitter-package-lock.json
  • Updates Directory.Generation.Packages.props in azure-sdk-for-net with AzureGeneratorVersion property
    • This version is used by management plane libraries that reference the Azure.Generator NuGet package
  • This step is skipped if the management plane generator directory doesn't exist

Step 7: Prepare Library List

  • Confirms the list of libraries to regenerate
  • Applies the -Libraries filter when specified
  • When no -Select flag, loads all libraries from Library_Inventory.md including:
    • Data plane libraries using @azure-typespec/http-client-csharp
    • Data plane libraries using @typespec/http-client-csharp
    • Management plane libraries using @azure-typespec/http-client-csharp-mgmt
  • In interactive mode (-Select), uses the previously selected libraries

Step 8: Regenerate Libraries

  • Pre-Install tsp-client: Runs npm ci --prefix eng\common\tsp-client once before parallel execution
    • Installs shared TypeSpec compiler tooling used by all libraries
    • Eliminates concurrent npm conflicts during parallel regeneration
  • Parallel Execution: Uses PowerShell's ForEach-Object -Parallel to regenerate multiple libraries concurrently
  • Cross-Platform CPU Detection:
    • Windows: Uses Get-CimInstance with Win32_Processor
    • Linux: Uses nproc command
    • macOS: Uses sysctl -n hw.ncpu command
  • Throttle Limit: Automatically calculated as (CPU cores - 2) with a minimum of 1 and maximum of 8
  • Skip Redundant Installs: Each parallel job uses /p:SkipTspClientInstall=true to skip re-installing tsp-client
  • Thread-Safe Progress: Real-time updates showing [completed/total] ✓/✗ LibraryName
    • Success: Green with ✓
    • Failure: White with ✗ (red reserved for error details in final report)
  • Isolated Execution: Each library regenerates in its own directory (no conflicts)
  • Runs dotnet build /t:GenerateCode /p:SkipTspClientInstall=true in the library's project directory
  • Automatically detects if the .csproj is in a src subdirectory
  • Tracks success/failure for each library with colored output

Step 9: Restore Artifacts (On Success Only)

If all libraries regenerate successfully, the script restores modified files:

  • eng/azure-typespec-http-client-csharp-emitter-package.json
  • eng/azure-typespec-http-client-csharp-emitter-package-lock.json
  • eng/http-client-csharp-emitter-package.json
  • eng/http-client-csharp-emitter-package-lock.json
  • eng/azure-typespec-http-client-csharp-mgmt-emitter-package.json
  • eng/azure-typespec-http-client-csharp-mgmt-emitter-package-lock.json
  • eng/packages/http-client-csharp/package-lock.json
  • eng/packages/http-client-csharp-mgmt/package.json
  • eng/packages/http-client-csharp-mgmt/package-lock.json
  • eng/centralpackagemanagement/Directory.Generation.Packages.props
  • NuGet.Config

Note: If any libraries fail, artifacts are NOT restored, allowing you to debug the issue with the modified configuration intact.

Shared Regeneration Helpers

The library discovery and parallel regeneration logic lives in RegenPreview.psm1 so it can be reused outside of local validation runs:

Function Description
Get-SdkLibrariesToRegenerate Scans sdk/ in azure-sdk-for-net and returns the libraries whose tsp-location.yaml references one of the TypeSpec C# emitter package json artifacts. Supports filtering by emitter (-EmitterPackageJsonPaths).
Invoke-SdkLibraryRegeneration Pre-installs tsp-client, pre-builds the code generation plugin, and then regenerates the given libraries in parallel with dotnet build /t:GenerateCode. Supports -ThrottleLimit (defaults to 3x the logical processors, clamped to 4-12), -NpmRegistry, -AdditionalBuildArgs, -SerialServiceDirectories (service directories that must be regenerated one library at a time), and -StopOnFailure.
Write-RegenerationReport Prints the pass/fail summary and optionally writes the detailed JSON report. In CI mode, it also prints a human-readable JSON report.

Error Handling

If the script encounters an error during pre-requisite steps (Steps 1-6), it will:

  • Display the error message and stack trace
  • Attempt to restore NuGet.Config to prevent leaving the repository in an inconsistent state
  • Exit with code 1

Output

Debug Folder

All packaged artifacts are stored in the debug folder at the root of the unbranded generator:

  • typespec-http-client-csharp-{version}.tgz - Unbranded generator npm package
  • azure-typespec-http-client-csharp-{version}.tgz - Azure generator npm package
  • Microsoft.TypeSpec.Generator.{version}.nupkg - Core generator NuGet package
  • Microsoft.TypeSpec.Generator.Input.{version}.nupkg - Input models NuGet package
  • Microsoft.TypeSpec.Generator.ClientModel.{version}.nupkg - Client model NuGet package
  • regen-report.json - Detailed JSON report of regeneration results (written to the ADO artifact staging
    directory during CI runs)

Console Output

The script provides colored console output with:

  • Cyan: Step headers and section dividers
  • Yellow: Important information (versions, library counts, warnings)
  • Gray: Detailed command execution and file operations
  • Green: Success messages
  • Red: Error messages and failed libraries
  • White: Library names during regeneration

Regeneration Report

After regeneration completes, a summary report is displayed:

text
==================== REGENERATION REPORT ====================
Total Libraries: 3
Passed: 2
Failed: 1
Execution Time: 00:02:45

PASSED LIBRARIES:
  ✓ Azure.AI.VoiceLive (ai)
  ✓ Azure.AI.Projects (cognitiveservices)

FAILED LIBRARIES:
  ✗ Azure.Messaging.EventGrid.Namespaces (eventgrid)
    Error: Generation failed with exit code 1
    Details: ...

=============================================================
Detailed report saved to: C:\...\debug\regen-report.json

In ADO CI runs, the detailed JSON report is written to the artifact staging directory instead and is
also printed to the console in human-readable JSON. If any library fails to regenerate, later regeneration
batches are marked as skipped and the script exits with an error after printing the report. Local runs keep
the existing behavior: all regeneration batches run, and any library failures are reported as warnings at
the end.

Common Scenarios

Scenario 1: Test OpenAI Library Changes

powershell
# You made changes to the unbranded generator that affect OpenAI
# Regenerate the OpenAI library to validate your changes
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\openai-dotnet"

Scenario 2: Test a Small Change Quickly (Azure SDK)

powershell
# You want to test Azure generator changes on specific libraries only
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Libraries "Azure.ResourceManager.AppContainers, Azure.Data.AppConfig"

Scenario: Full Validation Before PR

powershell
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net"

Scenario 4: Regenerate Azure Spector Test Scenarios

powershell
# Validate that local changes don't break Azure spector test scenarios
.\RegenPreview.ps1 -SdkLibraryRepoPath "C:\repos\azure-sdk-for-net" -Spector

Website Src Content Docs Release Notes Cadl Typespec Migration (`website/src/content/docs/release-notes/cadl-typespec-migration.md`)


title: Cadl to TypeSpec rename completed in March 2023 release

As you may recall from our previous email to partners, we are renaming the product as it becomes more mature, stable and one step closer to release.

With the 2023-03-13 release, We are pleased to announce that we have completed the process of changing the name of our product from Cadl to TypeSpec. This marks another important milestone. The new name better aligns with the functionality and benefits of the product, and we believe it will make it easier for our customers to understand and remember.

We understand that change can be challenging, and we appreciate your patience and support during this process.

Name Changes

  • Packages:
    • NPM package scope:

      • @cadl-lang/[xx] -> @typespec/[xx]
    • Package names

Old Package Name Old Namespace New Package Name New Namespace
@cadl-lang/compiler using Cadl; @typespec/compiler using TypeSpec;
@cadl-lang/rest using Cadl.Http; @typespec/http; using Http;
@cadl-lang/rest using Cadl.Rest; @typespec/rest using Rest;
@cadl-lang/openapi using Cadl.OpenApi; @typespec/openapi using OpenAPI;
@cadl-lang/openapi3 using Cadl.OpenApi3; @typespec/openapi3 using OpenAPI3;
  • File extension:

    • .cadl -> .tsp
  • Configuration file:

    • cadl-project.yaml -> tspconfig.yaml
  • CLI

    • npx cadl compile . -> npx tsp compile .
  • Compiler JS APIs

    • All artifacts with *Cadl* in the name have been updated to *TypeSpec*. However, aliases have been created with older *Cadl* name with @deprecated flag.
  • Noteable changes:

    • @cadl-lang/rest was split into two packages, @typespec/rest and @typespec/http
  • Back-compatibility

    • .cadl files are continue be recognized by compiler.
    • cadl-project.yaml is still supported if tspconfig.yaml not found

Migration tool

An experimental migration tool has been introduced to take care of many of the manual migration steps. Just execute following command in your TypeSpec folder.

bash
npx @typespec/migrate

If you would like execute from a different folder or don't have a package.json that indicates compiler package versions, please see command line options:

bash
npx @typespec/migrate --help

The migration tool will perform following steps:

  • Rename cadl-project.yaml to tspconfig.yaml.
  • Update tspconfig.yaml format to new emit schema if necessary.
  • Rename .cadl files to .tsp.
  • Update import and using statements in any .tsp files
  • Update package.json with new package name and versions.

Website Src Content Docs Release Notes Release 2022 07 08 (`website/src/content/docs/release-notes/release-2022-07-08.md`)


title: July 2022 releaseDate: 2022-07-08

This release contains breaking changes

  • Emitter options normalized to use kebab-case instead of camelCase.
  • @serviceHost decorator replaced by @server decorator
  • Versioning decorators now use enumerated values instead of strings

Emitter options

This release brings a stricter option definition for emitters and requires usage of those options to be specified with the fully qualified name to prevent conflicts.
All options have also been renamed to match kebab-case naming.
The options can also be specified via the tspconfig.yaml file.

Migrating Command Line Scripts

If you had for example --option operationPollingLocation=tenant

  1. Use tspconfig.yaml project file Recommended

    If you don't have that file yet, create it next to package.json, this file can be used to configure the emitters.

    yaml
    emitters:
      <fully-qualified-emitter-package-name>:
          <optionName>: <optionValue>
    
    # For example
    emitters:
      @typespec/openapi3:
        output-file: ./openapi.json
  2. Via the --option flag

    You can still use the --option flag but you'll need to specify the fully qualified name of the option.

    bash
    --option @<emitter-package>.<optionName>=<optionValue>
    
    # For example
    --option @typespec/openapi3.output-file=openapi.json

Renamed Emitter Options

Before Now
@typespec/openapi3
outputFile output-file

`@serviceHost` decorator replaced with `@server` decorator

The @serviceHost decorator that decorated the root namespace was used to specify the domain name of the base service endpoint. This functionality has been replaced by the @server decorator, which allows specifying full and parametrized Uris for the service endpoint, as described here

Before

typespec
@serviceHost("example.com")
namespace MyService;

After

typespec
@server("https://example.com")
namespace MyService;

Versioning uses enums instead of strings

Versions must now be specified using string-valued enumerations, and each of the versioning decorators must reference an enum value rather than using the version string directly.

typespec
// Before
@versioned("2021-01-12" | "2022-01-15-preview")
namespace Api;

// After
@versioned(Versions)
namespace Api;

enum Versions { v2021_01_12: "2021-01-12", v2022_01_15_preview: "2022-01-15-preview" }
typespec
// Before
@added("2022-01-15-preview")
model Foo {}

// After
@added(Versions.v2022_01_15_preview)
model Foo {}

Website Src Content Docs Release Notes Release 2022 08 10 (`website/src/content/docs/release-notes/release-2022-08-10.md`)


title: August 2022 releaseDate: 2022-08-10

This release contains breaking changes

  • Operation parameters without decorators
  • OkResponse is no longer a template
  • Route resolution changes
  • Remove Map type
  • @path may not decorate optional properties or parameters without a default value

Operation parameters without decorators

A single undecorated (not marked @query, @header, @body or @path) operation parameter will now become a property of the request body rather than have its type define the request body. This allows defining the body with multiple unannotated parameters, which can include unannotated properties that are spread into parameters. (Previously, more than one unannotated parameter was an error.)

For example, the following used to define a request body of type string, but now defines a request body that is an object with a property named body of type string.

typespec
op create(body: string): void;

To get the previous behavior, the parameter now needs to be explicitly marked with @body:

typespec
op create(@body body: string): void;

OkResponse is no longer a template

Previously, OkResponse took an argument for the body type. Now it is a simple model like the other XxxResponse types. Alone, it implies a status code of 200 with no body.

Since 200 is the default status code for non-empty bodies, you can usually replace OkResponse<T> with simply T.

typespec
op get(id: string): OkResponse<Pet>;

Can be:

typespec
op get(id: string): Pet;

In certain situations where the body type is not (necessarily) a model, you will need to use the new Body<T> type. For example.

typespec
op list(): OkResponse<Pet[]>;

Can become:

typespec
op list(): OkResponse & Body<Pet[]>;

Since 200 status code is used by default, this could also be:

typespec
op list(): Pet[];

Generic models based on OkResponse<T> may also require Body<T>. For example:

typespec
model MyResponse<T> {
  ...OkResponse<T>;
  @header example: string;
}

Since T is not constrainted to be a model, it might be an intrinsic type, an array, or the like, the template should be changed to use Body<T>:

typespec
model MyResponse<T> {
  ...OkResponse;
  ...Body<T>;
  @header example: string;
}

In general, the prior OkResponse<T> is equivalent to OkResponse & Body<T> now or, equivalently, { ...OkResponse, ...Body<T> }. In practice there are many situations where you can leave out OkResponse altogether and use plain T rather than Body<T>.

See also https://typespec.io/docs/libraries/http/#request--response-bodies

Route resolution changes

Resolving operation routes now follows the following logic:

  • if there is a service namespace specified
    • only emit the operations and interfaces under that namespace(recursively)
  • if not:
    • only emit the operations and interfaces defined at the root (DO NOT look into namespaces)

Action if applicable

  • If a TypeSpec spec used a service namespace without @serviceTitle add the @serviceTitle decorator to the service namespace, otherwise no routes will be emitted.
  • If a TypeSpec spec contains service namespaces that are not child namespaces of the service namespace, move these namespaces under the service namespace.

Cases

Operation at the root

typespec
op test(): void;

✅ Stay the same

Before After
["/"] ["/"]

Operation in namespace (not service namespace)

typespec
namespace DemoService;

op test(): void;

⚠️ Output stays the same but add warning that no routes are emitted

Before After
[] []

Operation in namespace (not service namespace) with @route

typespec
namespace DemoService;

@route("/")
op test(): void;

⚠️ Now the same as previous case, no routes emitted and emit warning

Before After
["/"] []

Resolve by adding the `@serviceTitle` decorator

Add @serviceTitle to the namespace

typespec
@serviceTitle("DemoService")
namespace DemoService;

@route("/")
op test(): void;

Operation in service namespace

typespec
@serviceTitle("My Service")
namespace Foo;

op test(): void;

✅ Stay the same

Before After
["/"] ["/"]

Operation in namespaces other than the service namespace

typespec
import "@typespec/rest";

using Http;

@serviceTitle("My Service")
namespace Foo {
  @route("in-service")
  op test(): void;
}

namespace MyLib {
  @route("my-lib")
  op test(): void;
}

⚠️ Other namespace routes are not included anymore

Before After
["/in-service", "my-lib"] ["/in-service"]

Resolve by making additional namespaces children of the service namespace

Make any added namespaces children of the service namespace

typespec
import "@typespec/rest";

using Http;

@serviceTitle("My Service")
namespace Foo {
  @route("in-service")
  op test(): void;
}

namespace Foo.MyLib {
  @route("my-lib")
  op test(): void;
}

Remove Map type

Map type was removed. Usages of Map<string, T> can be replaced with new type Record<T>. Other usages of Map may be replaced with object.

Map using string key type

typespec
model Foo {
  options: Map<string, string>;
}

Replace with `Record`

typespec
model Foo {
  options: Record<string>;
}

Map using non-string key type

typespec
model Foo {
  options: Map<int32, string>;
}

Replace with `object`

typespec
model Foo {
  options: object;
}

`@path` may not decorate optional properties or parameters without a default

Properties and parameters marked with the @path decorator should be required, but may be optional if they have a default value

optional path parameters

typespec
model Foo {
  @path
  name?: string;
}

Was a bad practice, but was allowed in previous versions. This will now throw an error diagnostic.

Resolve by making the property required

typespec
model Foo {
  @path
  name: string;
}

Resolve by adding a default value

typespec
model Foo {
  @path
  name?: string = "singleton";
}

Website Src Content Docs Release Notes Release 2022 09 07 (`website/src/content/docs/release-notes/release-2022-09-07.md`)


title: September 2022 releaseDate: 2022-09-07

This release contains breaking changes:

  • Api: Remove Type suffix
  • TypeSpec: Rename Page to CollectionWithNextLink
  • Api: Enum.members change typed from EnumMember[] to Map<string, EnumMember>
  • TypeSpec Api: Deprecation of @consumes @produces decorators

**[API]** `Type` suffix removed from most `Type`s

The following types have been renamed in the compiler API:

  • ModelType -> Model
  • ModelTypeProperty -> ModelProperty
  • InterfaceType -> Interface
  • EnumType -> Enum
  • EnumMemberType -> EnumMember
  • TemplateParameterType -> TemplateParameter
  • NamespaceType -> Namespace
  • OperationType -> Operation
  • StringLiteralType -> StringLiteral
  • NumericLiteralType -> NumericLiteral
  • BooleanLiteralType -> BooleanLiteral
  • TupleType -> Tuple
  • UnionType -> Union
  • UnionTypeVariant -> UnionVariant
  • ProjectionType -> Projection

Update uses of the type names on the left with the type names on the right. The old names can still be used but they are deprecated and may be removed from a future release.

In library @typespec/rest in TypeSpec.Rest.Resources namespace the Page model was renamed to CollectionWithNextLink.
This resolve an issue with Page being to generic of a name for this very opinionated model.

Before

typespec
op test(): Page<MyResource>;

Rename to

typespec
op test(): CollectionWithNextLink<MyResource>;

**[Api]** `Enum.members` change to `Map` [#953](https://github.com/microsoft/typespec/pull/953)

Changed the type of the members property of Enum from EnumMember[] => Map<string, EnumMember>

diff
-members: EnumMember[];
+members: Map<string, EnumMember>

[TypeSpec] [Api] **Deprecation**: `@consumes` `@produces`

Consumption of the data set by decorators was already removed in a previous release. Using them was a no-op.

The following are deprecated and will be removed in next release.

  • @consumes
  • @produces
  • getConsumes
  • getProduces

Alternative is to use a @header contentType: <contentType> property on the operation parameters or return type


Website Src Content Docs Release Notes Release 2022 10 12 (`website/src/content/docs/release-notes/release-2022-10-12.md`)


title: October 2022 releaseDate: 2022-10-12

This release contains breaking changes:

  • TypeSpec: Cannot extends or is a model expression via alias
  • Api: Removed createProgram and changed compile parameter order
  • TypeSpec Deprecation @service decorator replacing @serviceTitle and @serviceVersion
  • TypeSpec Api: Move @discriminator to compiler

[TypeSpec] Cannot `extends` or `is` a model expression via alias [PR 1004](https://github.com/microsoft/typespec/pull/1004)

Using model expression for is or extends directly was already forbidden.

typespec
model IsModelExpr is {bar: string} {}

model ExtendsModelExpr extends {bar: string} {}

The following workaround was however tolerated. This PR remove this functionality.

typespec
alias ModExpr = {bar: string};
model IsModelExprWAlias is ModExpr {}

model ExtendsModelExprWAlias extends ModExpr {}

Use a named model instead of an alias.

[API] Removed `createProgram` and changed `compile` parameter order

createProgram has been removed in favor of compile. The new compile has the same parameter as createProgram

ts
// Before
createProgram(host, "main.tsp);

// After
compile(host, "main.tsp");

compile api was changed to match the same order as old createProggram

ts
// Before
compile("main.tsp", host);

// After
compile(host, "main.tsp");

[TypeSpec] Deprecation: @service decorator replacing `@serviceTitle` and `@serviceVersion`

  • @serviceTitle has been deprecated
  • @serviceVersion has been deprecated
typespec
// Before
@serviceTitle("Pet Store")
@serviceVersion("v1")
namespace PetStore;

// After
@service(#{"Pet Store", version: "v1"})
namespace PetStore;

This allows to specify the service namespace without any title or version

typespec
@service
namespace PetStore;

[TypeSpec] [Api] Move `@discriminator` to compiler

The @discriminator has been moved to the compiler. This means that if you were using the fully qualified name to reference the decorator @TypeSpec.Rest.disriminator it should be changed to @discriminator

No changes

typespec
using Rest;

@disriminator("kind")
model Pet {}

Before

typespec
@TypeSpec.Rest.disriminator("kind")
model Pet {}

After

typespec
@disriminator("kind")
model Pet {}

Change to api

the getDiscriminator accessor has also been removed into the compiler.

Before

ts
import { getDiscriminator } from "@typespec/rest";

After

ts
import { getDiscriminator } from "@typespec/compiler";

Website Src Content Docs Release Notes Release 2022 12 07 (`website/src/content/docs/release-notes/release-2022-12-07.md`)


title: December 2022 releaseDate: 2022-12-07

:::caution
This release contains breaking changes
:::

New features

Decorator signature help

With the decorator signature extern dec added in previous release and the doc comment support added in this version the IDE is able to provide a signature help for decorators

New `scalar` type

:::caution
This is a breaking change see details here
:::

This release introduce a new scalar type meant to represent what were previously models with no properties actually representing an scalar value.

typespec
scalar uuid extends string;

Emitter options interpolation and standardization

Emitter options are not able to interpolate other values. See details

yaml
output-dir: {cwd}/generated
options:
  @typespec/openapi3:
    emitter-output-dir: {output-dir}/openapi3

Breaking changes

`TypeSpec` `Api` `Compiler` Moved TypeSpec intrinsic types to a new Type `scalar`

A new type has been introduced to TypeSpec in order to differentiate scalar from structured models types. Those new types can be declared using scalar and can extend another scalar using extends.

Change to TypeSpec intrinsic models

All of TypeSpec intrinsic models have been updated to be scalar types instead of model. In most cases this shouldn't affect a spec but if you are defining a custom model that is one of the intrinsic types you'll have to update it

Before

text
model MyStr is string;

After

text
scalar MyStr extends string;

Migration script

The following script can be used to automatically migrate previous usages.

typespec
npx @typespec/migrate

Change to Compiler API

Removed:

  • getIntrinsicModelName: Check it is a std type with program.checker.isStdType(type) and then use the type.name otherwise look for type.baseScalar
  • isIntrinsic: Check it is a std type with program.checker.isStdType(type) or just remove the check if goal was to omit intrinsic types from models.

Migration:

  1. Add a new switch to handle the new scalar type
  2. To resolve a scalar type, check if its a std type using program.checker.isStdType(type)
  3. If yes, do the same behavior has before with getIntrinsicModelName
  4. If not, call scalar.baseScalar and repeat step 2.

`API` - Change to `onEmit` signature

The signature of onEmit has been updated to accommodate for changes to the emitter options

Before

ts
export function $onEmit(program: Program, options: MyEmitterOptions);

Now

ts
export function $onEmit(context: EmitContext);

Access properties

Before Now
program context.program
options context.options
options["output-dir"] context.emitterOutputDir See details

[TypeSpec] [Api] Emitter option `output-dir` renamed to `emitter-output-dir`

The option has been renamed for all emitters as emitter-output-dir is now managed in the compiler
Before

yaml
emitters:
  my-emitter:
    output-dir: /path/to-output-dir

Now

yaml
emitters:
  my-emitter:
    emitter-output-dir: {output-dir}/for-this-emitter
#                        ^ can now reference the common output-dir

Change to api

Before

ts
export function $onEmit(program: Program, options: MyEmitterOptions) {
  const outputDir = options["output-dir"];
}

Now

ts
export function $onEmit(context: EmitContext) {
  const outputDir = context.emitterOutputDir;
}

`API > @typespec/compiler` Multiple services per typespec specs are now allowed making a service accessor obsolete.

The following functions have been deprecated

ts
export function getServiceTitle(program: Program): string;
export function getServiceVersion(program: Program): string;
export function getServiceNamespace(program: Program): Namespace;
export function getServiceNamespaceString(program: Program): string | undefined;
export function setServiceNamespace(program: Program, namespace: Namespace);

and replaced with

ts
export interface ServiceDetails {
  title?: string;
  version?: string;
}
export interface Service extends ServiceDetails {
  type: Namespace;
}

export function listServices(program: Program): Service[];
export function getService(program: Program, namespace: Namespace): Service | undefined;
export function isService(program: Program, namespace: Namespace): boolean;

export function addService(program: Program, type: Namespace, details?: ServiceDetails);

Other changes to the service resolution:

  • Title won't be defaulted to (title)
  • Version won't be defaulted to 0000-00-00
  • Service namespace won't be defaulted to global namespace
Before To add support for multi serviceF
getServiceNamespace(program) listServices(program) to get all services
getServiceNamespaceString(program) getNamespaceName(service.type) is where service is getService(program, serviceNamespace)
getServiceTitle(program) service.title where service is getService(program, serviceNamespace)
getServiceVersion(program) service.version where service is getService(program, serviceNamespace)

`TypeSpec` Directives can no longer be placed in certain contexts.

For example, this is no longer valid:

typespec
model M<
#suppress example
T
> {}

And will now result in an error "Cannot place directive on template parameter".

Move such directives up to the nearest statement, interface operation, model property, operation parameter, union variant, or enum member.

For the above example, that would be:

text
#suppress example
model M<T> {}

Deprecations

:::caution
Deprecated items will be removed in 2 release cycle.
:::

`TypeSpec > @typespec/rest` - `@segmentSeparator` is deprecated. The `@actionSeparator` decorator should be used instead.

Before

typespec
@autoRoute
namespace Things {
  @action
  @segmentSeparator(":")
  @put
  op customAction1(
    @segment("things")
    @path
    thingId: string,
  ): string;
}

After:

typespec
@autoRoute
namespace Things {
  @action
  @actionSeparator(":")
  @put
  op customAction1(
    @segment("things")
    @path
    thingId: string,
  ): string;
}

`TypeSpec` Deprecated `uri` being replaced by `url`

Before:

typespec
model Server {
  endpoint: uri;
}

Now:

typespec
model Server {
  endpoint: url;
}

`Config` - Deprecated `emitters` property in the `tspconfig.yaml`

The emitters property of the configuration file has been deprecated and replaced by 2 new properties making it consistent with the CLI

Before:

yaml
emitters:
  "@typespec/openapi3":
    output-file: openapi3.json
  other-emitter: true

After:

yaml
emit:
  - "@typespec/openapi3"
  - "other-emitter"
options:
  @typespec/openapi3:
    output-file: openapi3.json

Website Src Content Docs Release Notes Release 2023 01 12 (`website/src/content/docs/release-notes/release-2023-01-12.md`)


title: January 2023 releaseDate: 2023-01-12

:::caution
This release contains breaking changes
:::

Breaking Changes

`@typespec/openapi3` Default output-file is `openapi.yaml`

As the openapi3 emitter added support for serializing the openapi document to yaml it changed the default to emit yaml to openapi.yaml.

yaml
options:
  @typespec/openapi3:
     file-type: json

or alternatively specify the name of the file you'd want

yaml
options:
  @typespec/openapi3:
     output-file: custom.json

[API] Templated operation inside interface

This breaking change affects libraries and emitters

  • templateArguments property on templated type is deprecated and has been replaced with more detailed templateMapper

    If you where you using the templateArgument to find out if something was a template instance change

    ts
    type.templateArguments.length > 0;

    to

    ts
    isTemplateInstance(type);
  • Interfaces might have some templated operations

    If blindly including all operations in an interface you might need to make sure the operation is not a template declaration first.

    For example with the following tsp code:

    typespec
    interface Factory {
      read<T>(): T;
      ping(): void;
    }

The emitter probably only wants to include the ping() operation but ignore read(). The isTemplateDeclaration helper can be used to filter out the template operations.


Website Src Content Docs Release Notes Release 2023 02 07 (`website/src/content/docs/release-notes/release-2023-02-07.md`)


title: February 2023 releaseDate: 2023-02-07

:::caution
This release contains breaking changes
:::

New Features

Emitter Framework

The new emitter framework makes it simpler to build emitters from TypeSpec to other assets. It provides an easy way to handle TypeSpec types and provides a template for asset emitters to spell out the emitter tasks. It provides helpers to solve many difficult problems, including:

  • Constructing references between types
  • Handling circular references
  • Propagating type context based on containers or references
  • Class-based inheritance to encourage reuse and specialization of existing emitters

Details are available on the documentation website or in the [repository documentation] (https://github.com/microsoft/typespec/blob/main/docs/extending-typespec/emitter-framework.md).

Cli support for emitters in development

Use the path to the emitter to specify emitter options, rather than the emitter name.

Allow `@autoroute` and `@route` to be used together

Provide standard rules for combining @route and @autoroute decorators

  • Allow @route applied to an operation to override route settings from the interface
  • Allow @route on an operation to prepend the paths provided by @autoroute

Simplified versioning dependencies

Introduced the @useDependency decorator to replace @versionedDependency decorator. Simplify dependencies between versioned namespaces, by allowing each version to be decorated with the appropriate versioned dependency, rather than requiring the user to construct a dependency map.

Breaking Changes

Breaking changes in this release resulted from removal of previously decorated types and functions, including:

  • uri scalar removed. Use the url scalar instead.
  • Removed deprecated metadata types with the 'type' suffix.
    • Use Enum instead of EnumType
    • Use EnumMember instead of EnumMemberType
    • Use Interface instead of InterfaceType
    • Use Model instead of ModelType
    • Use ModelProperty instead of ModelTypeProperty
    • Use Namespace instead of NamespaceType
    • Use Operation instead of OperationType
    • Use Tuple instead of TupleType
    • Use Union instead of UnionType
  • Removed Map<K, V> type, use Record<V> instead
  • Removed @serviceTitle and @serviceVersion decorators. Use the @service decorator instead.
  • Removed helper and accessor functions associated with @serviceTitle and @serviceVersion decorators
    • Replace getServiceNamespace, getServiceTitle, getServiceVersion, and getServiceNamespaceString with getService or listServices
    • Replace setServiceNamespace with addService
  • Removed @segmentSeparator, use @actionSeparator instead
  • Removed @produces and @consumes decorators. Use @header contentType: <ContentType> instead in the operation return type
  • Removed getSegmentSeparator function. Use getActionSeparator instead
  • Removed getProduces and getConsumes functions. Use getContentTypes instead

Deprecations

Deprecates the `@versionedDependency` decorator in favor of the `@useDependency` decorator

For versioned libraries, @useDependency is applied to the version enum members

Before (versioned namespace)

typespec
@armProviderNamespace
@service(#{ title: "Microsoft.Observability" })
@versionedDependency(
  [
    [Microsoft.Observability.Versions.v2021_06_13_preview, Azure.Core.Versions.v1_0_Preview_2],
    [Microsoft.Observability.Versions.v2022_04_30_preview, Azure.Core.Versions.v1_0_Preview_2]
  ]
)
@versionedDependency(
  [
    [
      Microsoft.Observability.Versions.v2021_06_13_preview,
      Azure.ResourceManager.Versions.v1_0_Preview_1
    ],
    [
      Microsoft.Observability.Versions.v2022_04_30_preview,
      Azure.ResourceManager.Versions.v1_0_Preview_1
    ]
  ]
)
@versioned(Versions)
namespace Microsoft.Observability;

interface Operations extends Azure.ResourceManager.Operations {}

enum Versions {
  v2021_06_13_preview: "2021-06-13-preview",
  v2022_04_30_preview: "2022-04-30-preview",
}

After (Versioned namespace)

typespec
@armProviderNamespace
@service(#{ title: "Microsoft.Observability" })
@versioned(Versions)
namespace Microsoft.Observability;

interface Operations extends Azure.ResourceManager.Operations {}

enum Versions {
  @useDependency(Azure.Core.Versions.v1_0_Preview_2, Azure.ResourceManager.Versions.v1_0_Preview_1)
  v2021_06_13_preview: "2021-06-13-preview",

  @useDependency(Azure.Core.Versions.v1_0_Preview_2, Azure.ResourceManager.Versions.v1_0_Preview_1)
  v2022_04_30_preview: "2022-04-30-preview",
}

For unversioned libraries that reference versioned libraries, simply replace @versionedDependency with @useDependency

Before (unversioned namespace using versioned library)

typespec
@service(#{ title: "Microsoft.EnvelopeTest", version: "2021-09-21-preview" })
@versionedDependency(Azure.ResourceManager.Versions.v1_0_Preview_1)
@armProviderNamespace
namespace Microsoft.EnvelopeTest;

After (unversioned namespace using versioned library)

typespec
@service(#{ title: "Microsoft.EnvelopeTest", version: "2021-09-21-preview" })
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1)
@armProviderNamespace
namespace Microsoft.EnvelopeTest;

Website Src Content Docs Release Notes Release 2023 03 13 (`website/src/content/docs/release-notes/release-2023-03-13.md`)


title: March 2023 releaseDate: 2023-03-13

:::caution
This release contains breaking changes

Please see Cadl to TypeSpec rename and migration FAQ
:::

New Features

Add--config option

Enable specifying path to configuration file

Allow escaping identifiers using backticks

Identifiers may contain reserved words or non-standard characters when surrounded with backticks (`), for example:

typespec
op `op`(): void;

Correct Order of Application for augment decorators

Augment decorators applied last

Added migration script for specs using Cadl

Migration script available for existing specs, see @typespec/migrate for details

Added public formatIdentifier function

Added public formatIdentifier function.

Visibility defaults to read

Default visibility is configurable, but default to 'read'.

Breaking Changes

Cadl renamed to Typespec

All core cadl packages moved from the @cadl-lang group to the @typespec group

Old Package Name New Package Name
@cadl-lang/compiler @typespec/compiler
@cadl-lang/rest @typespec/rest, @typespec/http
@cadl-lang/migrate @typespec/migrate
@cadl-lang/openapi @typespec/openapi
@cadl-lang/openapi3 @typespec/openapi3
@cadl-lang/versioning @typespec/versioning

`rest` library split into `http` and `rest` libraries

Basic http protocol support is in the http library, support for resources and ReST are in the rest library. The migration tool will correctly update imports in your spec to match the new
libraries.

Removed Support for Visual Studio 2019 in Visual Studio IDE extension

You must use a later version of Visual Studio

Removed `emitters` option in configuration

Use emit and options instead


Website Src Content Docs Release Notes Release 2023 04 11 (`website/src/content/docs/release-notes/release-2023-04-11.md`)


title: April 2023 releaseDate: 2023-04-11

:::caution
This release contains breaking changes
:::

New Features

@typespec/compiler

  • Emit diagnostic when ambiguous symbol exists between global and usings
  • Add support for referencing metatypes ModelProperty ::type and Operation ::returnType and ::parameters
  • Relax constraint on derived type overrides
  • Prevent use of augment decorators on instantiated templates.
  • Allow projectedNames helpers to work with previous projections
  • Add helper interpolatePath for emitter to do additional interpolation on config properties
  • Api: Update getTypeName to omit standard library namespace
  • Allow overloads in interfaces to work under projection
  • Add pre-projection support
  • Provide access to extended interfaces in type graph
  • Document member ordering and fix projection rename reordering

@typespec/openapi3

  • Support shared routes
  • Update to use new interpolatePath logic to resolve the output-file

@typespec/migrate

  • Add migrate script for zonedDateTime
  • Add migration script for @header and @query format required

Bug Fixes

@typespec/compiler

  • Fix: emitter framework will now visit scalar declarations
  • Fix: Alias unknown ref crash
  • Fix: Empty model expression assignable to array
  • Fix: tsp code uninstall not finding extension to uninstall
  • Fix: Issue where template parameter type check wouldn't work if constraint is exact same type as next validation

@typespec/rest

  • Fix: Issue where @action("") was treated the same as @action. Now this emits an error.

@typespec/versioning

  • Fix: Issue with using version as a template parameter across different namespaces. Includes a significant change in the versioning library internals.
  • Fix:Use pre-projections to fix issues with versioned resources.

VS Code extension

  • Fix: Highlighting of TypeSpec code blocks in markdown.

Breaking Changes

@typespec/versioning: Removed deprecated versioning symbols.

  • Removed @versionedDependency decorator. Use @useDependency instead.
  • Removed getRenamedFromVersion. Use getRenamedFromVersions instead.
  • Removed getRenamedFromOldName. Use getNameAtVersion instead.
  • Removed getAddedOn, addedAfter, getRemovedOn and removedOnOrBefore. Use existsAtVersion instead.
  • Removed renamedAfter. Use hasDifferentNameAtVersion instead.

@typespec/compiler: Removed `zonedDateTime` and replaced with `utcDateTime` and `offsetDateTime`

To disambiguate important time concepts and encourage best practice behavior in representing date-time types we
removed zonedDateTime which was being used to represent a time with offset, and replaced it with two new types that are more precisely defined:

  • utcDateTime which represents a precise instant in time. This type should be used to represent time values given in coordinated universal time (UTC) and unix timestamps. This is the preferred mechanism for capturing dateTime in most service specifications.
  • offsetDateTime which represents a time with offset. Note that while this time contains an offset from UTC, it does not contain any additional time zone information.

Change instances of `zonedDateTime` to `utcDateTime`

Old

typespec
model MyModel {
  createdAt: zonedDateTime;
}

New

typespec
model MyModel {
  createdAt: utcDateTime;
}

Use the (experimental) Migration tool to make this change

The (experimental) migration tool will automatically migrate instances of zonedDateTime to utcDateTime. To perform all migrations in a TypeSpec or Cadl specification package, execute:

bash
npx @typespec/migrate

If you would like to execute from a different folder or don't have a package.json that indicates compiler package versions, please see command line options:

bash
npx @typespec/migrate --help

@typespec/http: Removed header and query default format

@header and @query no longer default the format to csv and multi respectively. A value must now be provided when the type is an array. The migration tool can also be used to make this change.

@typespec/lint: Removed global state

Rules from another library can no longer be enabled on a LibraryLinter.


Website Src Content Docs Release Notes Release 2023 05 10 (`website/src/content/docs/release-notes/release-2023-05-10.md`)


title: May 2023 releaseDate: 2023-05-10

:::caution
This release contains breaking changes
:::

New Features

@typespec/compiler features

  • Add @encode decorator, with new formats for date and duration encoding support
    • "rfc3339", "rfc7231' for utcDateTime, offsetDateTime types
    • "unixTimestamp" support for utcDateTime type
    • "iso8601" support for duration types
    • "base64", "base64url" support for bytes types
  • Allow default values for required model properties and parameters
  • sourceModel property added to Model types
  • sourceOperation property added to Operation types
  • Model and Scalar types added to TypeSpec.Reflection namespace

@typespec/http features

  • add @sharedRoute decorator
  • allow union types within a response status code

@typespec/openapi3 features

  • Enhanced support for shared routes
  • Support for union types within a response status code

Bug Fixes

  • General fixes to generated reference documentation for all modules

@typespec/compiler fixes

  • Fix: compiler mismatch error recommendation
  • Fix: Interface with templated operation causing crash if defined after use
  • Fix: Issue with templated operations in templated interface would get cached only by keying on the operation template args.
  • Fix: missing-index diagnostic showing at the wrong location
  • Fix: --emit pointing to emitter js entrypoint resolve correct options
  • Fix: scalar template parameter name conflict with each other

@typespec/migrate fixes

  • Fix: incorrect resolution of tsp version
  • Fix: always loading current version of compiler

@typespec/openapi3 fixes

  • Fix openapi3 emitter to mark request body required
  • Fix issue where shared request bodies did not emit correctly.

@typespec/rest

  • Add validation to ensure that @action or @collectionAction operations have a specified name when used with @sharedRoute

@typespec/versioning

  • Fix: Crash during validation when using certain templated models from versioned library
  • Added validation preventing version enums from having duplicate values.
  • Fix issue where "is" dependencies were not detected.
  • Raise error if versioned spec specifies a single service version.

Breaking Changes

`@typespec/compiler`: `isTemplateDeclaration` will only return true for the original declaration and not partially instantiated templates

If library or emitter code needs to detect a partially instantiated template, the code will need to check isTemplateInstance and isTemplateDeclaration properties are false.

Deprecations

`@typespec/compiler`: Use `@encode` instead of `@format` for encoding scalars and properties of type `bytes`

The new @encode decorator should be used whenever the serialized type on the wire differs from the analogous TypeSpec type

Old deprecated usage of `@format` decorator

typespec
model Foo {
  @format("base64url")
  bar: bytes;
}

New usage of `@encode` decorator

typespec
model Foo {
  @encode("base64url")
  bar: bytes;
}

http: Use `@sharedRoute` instead of the `shared` property of `@route` options

The new @sharedRoute decorator replaces @route options for identifying operations that share a route

Old deprecated usage of `@route` decorator `shared` option

typespec
@route(
  "/doStuff",
  {
    shared: true,
  }
)
op doIntStuff(input: int32): int32;

@route(
  "/doStuff",
  {
    shared: true,
  }
)
op doStringStuff(input: string): string;

New usage of `@sharedRoute` decorator

typespec
@sharedRoute
@route("/doStuff")
op doIntStuff(input: int32): int32;

@sharedRoute
@route("/doStuff")
op doStringStuff(input: string): string;

Website Src Content Docs Release Notes Release 2023 06 06 (`website/src/content/docs/release-notes/release-2023-06-06.md`)


title: June 2023 releaseDate: 2023-06-06

:::caution
This release contains breaking changes
:::

New Features

  • Json Schema Emitter
  • New decimal types
  • Support for using doc comments instead of the @doc decorator
  • New valueof type constraint modifier for type literals
  • Lazy evaluation of emitter configuration
  • Signature Help for Template Arguments and other IDE improvements

New Decimal Types

Added support for decimal and decimal128 types

typespec
model Foo {
  price: decimal;
  shortPrice: decimal128;
}

These types represent values stored in decimal format which are often used in financial calculations to avoid rounding errors introduced by binary floating point formats. decimal can store values of any size and precision, while decimal128 corresponds to the 128 bit decimal floating point format described by IEEE 754.

Support for Doc Comments

Doc comments (/** */) will be treated as the documentation for types, unless there is an explicit @doc decorator

typespec
/** This will now be the documentation for model Foo */
model Foo {
  name: string;
}

New `valueof` type constraint modifier to require type literals

Templates and decorators can now constrain arguments to literal types. For example, the following template constraint:

typespec
model Foo<T extends valueof string> {
  @doc(T)
  name: string;
}

would match quoted literal strings, "this", as well as references to enum values. Templates and decorators that require string values should change to using this new formulation.

Allow lazy evaluation of emitter config

  • Validate linter configuration only for linters that are executing. This removes warnings for emitters that are configured but not available in a particular compilation.

Enable Signature Help for Template Arguments

  • Template completion will now have similar help in IDE language server as decorators currently do.

Show documentation Comments on Aliases in IDE Language Tooling

  • Documentation comments will show up in the IDE, just as documentation comments for other library types.

Ensure a dev release for all packages

  • Packages with no changes will now release with a -dev.0 version in dev releases.

Json Schema Emitter

  • Core packages now contain a Json Schema emitter @typespec/json-schema

Bug Fixes

-tspconfig.yaml should always get resolved relative to the entrypoint.

  • Array and Record doc comments are treated as developer documentation.

  • Fixed formatting of comment between decorator and op statement.

  • Decorators on operations may reference the same operation, or may reference an operation that references this operation.

typespec
interface FooOperations {
  @nextPageOperation(getNextPage) // allowed
  getNextPage(pageLink: url): Page<Foo>;
}
  • Fix validation for unixTimeStamp.

  • Fix diagnostic for intrinsic type expected to not assume string as the required type.

  • Fix error message for @encode errors.

  • Fix error handling when template is missing or invalid in tsp init.

  • Emitter framework: uppercase type argument type names when constructing a declaration name from a template instantiation.

  • Add reference documentation for missing decorators.

  • Remove dependency on node-fetch.

  • Remove inaccurate output directory message from compilation success.

  • Allow template resolution over http redirects for tsp init templates.

  • Improve signature help for an unterminated argument list.

New Deprecations

  • Templates that use decorators with the valueof constraint will receive a warning if their input types are not similarly constrained.
typespec
model Foo<T extends string> {
  @doc(T) // valueof constraint here will cause a warning
  id: uuid;
}

should be changed to apply the valueof constraint to their parameters that are passed to such decorators:

typespec
model Foo<T extends valueof string> {
  @doc(T) // no warning
  id: uuid;
}
  • The object type is deprecated. Templates and decorators should use TypeSpec.Reflection.Model to indicate a model contraint. In specs, {} designates and empty model, unknown[] indicates and empty array, and Record indicates a keyed type with unknown property types.

Breaking Changes

  • Decorators that require literal type value marshalling MUST change to using the valueof keyword.
typespec
extern dec myDecorator(target: Type, name: string);

Must be changed to use the valueof decorator

typespec
extern dec myDecorator(target: Type, name: valueof string);
  • Arrays are no longer assignable to an empty model (or object)

  • Remove `@format("url") from url scalar. OpenAPI emitters will still emit the appropriate format, emitters should rely on the scalar type to determine how to process the url type, not the format string.


Website Src Content Docs Release Notes Release 2023 07 11 (`website/src/content/docs/release-notes/release-2023-07-11.md`)


title: July 2023 releaseDate: 2023-07-11

:::caution
This release contains breaking changes
:::

New Features

`@typespec/compiler` and core language

  • New built-in linter system. Typespec libraries are able to define linting rules which can be configured in tspconfig.yaml. See documentation for configuring a linter and writing a linter
  • Allow decorators, directives and doc comments to be specified in any order
  • Format doc comments above decorators and directives
  • Improve formatting for properties with comments and decorators
  • Support for new Unicode 15.0 identifier characters

Emitter framework

  • Add writeOutput to TypeEmitter for additional customization in writing generated files
  • Add meta property to source files and declarations to store arbitrary metadata
  • Add support for emitting enum member references
  • Add new TypeEmitter methods for scalar instantiation

`@typespec/http` library

  • Add documentation on automatic header name resolution from property name for @header decorator

`@typespec/json-schema` emitter

  • Support @extension for adding arbitrary vendor extensions into the output
  • Add support for Record<T>
  • Support templates instantiated with intrinsic types and type expressions
  • Export emitter and related types from the npm package
  • By default, types that are not marked with @jsonSchema or are within a namespace with @jsonSchema are bundled into the schemas that reference them. Set the emitAllRefs option to true to get the previous behavior of emitting all types referenced as JSON Schema

`@typespec/openapi3` emitter

  • Omit x-typespec-name extension by default from openapi3 output. A new configuration and command line option include-x-typespec-name: \"inline-only\" | \"never\" has been added to get previous behavior

`@typespec/rest` library

  • Add isListOperation function migrated from @typespec/compiler

Bug Fixes

`@typespec/compiler` and core language bug fixes

  • Add support for UTF-8 with bom for other files loaded by TypeSpec compiler. .tsp files already had support, this make it more available for any library/emitter using the compiler api to load a file.
  • Fix signature help after comment with no closing parenthesis or angle bracket
  • Doc comment /** */ should override base type doc in model is or op is
  • Formatter incorrectly formatting :: to .
  • Fix formatting issue with comment between decorator and scalar or interface
  • Fix stack overflow when a circular reference with op is exists inside an interface.
  • Clean up handling of non-standard entrypoints in language server
  • Fix stack overflow when referencing decorator target in checker and projections

`@typespec/openapi3` emitter bug fixes

  • OpenAPI emitters: treat union namespaces like namespaces for other types

Emitter framework bug fixes

  • Fix that some context methods were not being passed the expected parameters
  • Fix that context was set incorrectly for some TypeEmitter methods, and add missing context methods for model properties, enum members, and union variants

`@typespec/json-schema` emitter bug fixes

  • Fix a bug that could result in a schema being bundled more than once

Breaking Changes

For Specification Authors

  • Update tsp init template schema for future extensibility. Older tsp version will fail validation when trying to initialize a project with a new template definition.
  • Json Schema Emitter: the namespace has been corrected to TypeSpec.JsonSchema. Update any using statements from JsonSchema to TypeSpec.JsonSchema, and any references from JsonSchema.[Type] to TypeSpec.JsonSchema.[Type].

For Emitters and Libraries

  • Minimum version of TypeScript updated to 5.0. TypeSpec is using new features available in TypeScript 5.0 which result in a definition file not parsable by older version. Note that this only affect compiling TypeScript code and is not breaking any JS or TypeSpec code. See more information on typescript 5.0
  • Emitter Framework: Add support for templates instantiated with types without declared names. In such cases, TypeEmitter's declarationName method may return undefined, and so the various *Instantiation methods might be called with an undefined name, and AssetEmitter's emitDeclarationName method might return undefined.
  • Make internal @resourceTypeForKeyParam decorator in @typespec/rest library private

Deprecations

  • @list decorator from TypeSpec core is deprecated. Use @listsResource from @typespec/http instead
  • isListOperation function from TypeSpec core is deprecated. Use isListOperation from @typespec/http instead
  • getListOperationType from TypeSpec core is deprecated
  • @typespec/linter Package is deprecated in favor of built-in linter system

Website Src Content Docs Release Notes Release 2023 08 08 (`website/src/content/docs/release-notes/release-2023-08-08.md`)


title: August 2023 releaseDate: 2023-08-08

:::caution
This release contains breaking changes
:::

New Features

`@typespec/compiler` New Features

  • Allow unnamed variants in named unions
  • Add #deprecated directive to replace @deprecated decorator, allowing deprecation any language element
  • Add a new util resolveCompilerOptions to resolve compiler options from a given entrypoint. This will resolve the options from the tspconfig.yaml in the same way the cli would.
  • Improvements to compiler watch mode. Files loaded in previous compilation will be watched for changes.
  • Allow library dependency versions to be specified in init templates using the form { name: \"the-lib\", version: \"1.0.0\" }
  • Update init template version compare to be greaterThanAndEqual instead of greaterThan.

`@typespec/http` New Features

  • Update model, operation and template docs for OpenAPI developers
  • Add collection format support for query and header properties: simple, form, ssv, tsv, pipes

`@typespec/openapi` and `@typespec/openapi3` New Features

  • Add new @info decorator providing the ability to specify the additional fields from openapi info object.
  • Support collection format support for query and header properties: simple, form, ssv, tsv, pipes
  • Change datetimes encoded with rfc7231 to produce format http-date instead of date-time-rfc7231 to be inline with Openapi format registry

Bug Fixes

`@typespec/compiler` Bug Fixes

  • Fix: Compiler version mismatch error would fire incorrectly
  • Fix some issues with not reporting deprecation on template constraints
  • Fix issue where using augment decorators on spread model properties, enum members or operations extended from parent interface would have no effect.
  • Fix issue where using augment decorator on operation parameters applied to source operation parameter as well.
  • Fix warn-as-error configuration in tspconfig.yaml was ignored
  • Emit diagnostic for an unresolved metatype property reference
  • Fix crash when using argument is not a namespace

`@typespec/openapi3` Bug Fixes

  • Fix: Emit clear diagnostics for empty unions and enum types
  • Fix: Apply @minItems and @maxItems decorators on model array

`@typespec/jsonschema`

  • Fix: Crash when using interfaces inside a @jsonSchema namespace

Breaking Changes

`@typespec/compiler` Breaking Changes

  • Emitter Framework: sourceFile method can return a Promise. This allows running async processes when generating the file content, such as a formatter. This results in a potential breaking change if calling emitSourceFile where you'll have to add await before.
  • The helper function formatTypeSpec is now async. Formatter was updated to use prettier 3.0.

Website Src Content Docs Release Notes Release 2023 09 12 (`website/src/content/docs/release-notes/release-2023-09-12.md`)


title: September 2023 releaseDate: 2023-09-12

:::caution
This release contains breaking changes
:::

New Features

`@typespec/compiler` New Features

  • Allow library authors to precisely define the visibility of operation templates with new decorators @parameterVisibility and @returnTypeVisibility, paired with corresponding accessor methods getParameterVisibility and getReturnTypeVisibility.
  • Add coloring for documentation comments in Visual Studio and Visual Studio Code language extensions.
  • Change yaml parser from js-yaml to yaml, enabling more precise validation and error reporting in tspconfig.yaml
  • Added validation to ensure that properties of a derived model (using extends) are compatible with the base model indexer.
  • Export CompilerOptions TypeScript type.
  • Report library information when library code crashes during $onValidate execution.
  • Diagnostics reported on a node with an id property will highlight the id instead of the entire node. For example, if a diagnostic is reported on a model type, the model name will be highlighted, instead of the entire model definition.

`@typespec/http` New Features

  • Add new helper method resolveRequestVisibility which incorporates settings from new core decorators @parameterVisibility and @returnTypeVisibility. This replaces the deprecated getRequestVisibility helper.
  • Add new helper method getDefaultVisibilityForVerb which returns the default visibility for Http verbs.
  • Add validation step to issue a warning when an operation is statement does not include route information from the original operation container (interface or namespace). This helps to prevent unexpected route changes when using operation references.
  • Add validation to prevent runtime errors when OAuth2Auth is given invalid OAuth2Flow arguments.

`@typespec/json-schema` New Features

  • Support decimal scalar types.

`@typespec/openapi3` New Features

  • Allow library authors to override default Http verb visibility in operation declarations.

`@typespec/protobuf` New features

  • Support omit-unreachable-types option.
  • Automatically convert empty operation parameters into a reference to google.protobuf.Empty instead of synthesizing an empty model.

`typespec-vscode` New features

  • Add color definition for @param tag in documentation comments

Bug Fixes

`@typespec/compiler` Bug Fixes

  • Fix issue where specifying a directory using the --config command line option would search the parent folder hierarchy looking for tspconfig.yaml.
  • Fix Stack overflow when a model property references itself.
  • Fix compiler crash when using the alias of a namespace that has decorators.
  • Fix compiler freeze when an invalid token appears between doc comment and type.
  • Fix compiler crash when relating recursive types.
  • Fix typo in format error diagnostic.

`@typespec/openapi3` Bug Fixes

  • Fixed issue where parameters marked with visibility "create" did not appear in a PATCH request body.
  • Fixed incorrect usage of exclusiveMinimum and exclusiveMaximum boolean properties.
  • Use anyOf instead of oneOf for operations with shared routes and different request or response bodies.
  • Fix handling of Record<T> in model is and model extends statements.

`@typespec/versioning` Bug Fixes

  • Fixed validation in @typeChangedFrom for incorrect versioned references.

Deprecations

`@typespec/compiler` Deprecations

  • The @deprecated decorator has been marked as deprecated. Use #deprecated directive instead.

`@typespec/http` Deprecations

  • The getRequestVisibility helper function is now deprecated. Use resolveRequestVisibility or getDefaultVisibilityForVerb instead.

Breaking Changes

`@typespec/compiler` Breaking Changes

  • A semicolon is now required at the end of augment decorator statements. The formatter was already inserting the semicolon, if omitted. For example:

    tsp
    @@doc(MyType.myProperty, "Documentation for myProperty");

Website Src Content Docs Release Notes Release 2023 10 11 (`website/src/content/docs/release-notes/release-2023-10-11.md`)


title: October 2023 releaseDate: 2023-10-11

:::caution
This release contains breaking changes
:::

New Features

`@typespec/compiler` New Features

  • The never type is now assignable to all types.
  • Allow constraint decorators (e.g. @minValue, @maxValue, @maxLength, etc.) to be applied to nullable types.
  • Add support for @returns and @errors doc comment tags.
    • @returns(or @returnsDoc decorator) can be used to describe the success return types of an operation.
    • @errors(or @errorsDoc decorator) can be used to describe the error return types of an operation.
  • Skip emit of deprecated diagnostic for a type reference that is used in a deprecated declaration statement.
  • Improve website performance by removing decorators export and importing decorators individually.

`@typespec/http` New Features

  • Add support for @returns and @errors doc comment tags.
  • Add support for status code ranges for http responses. Change to API Http responses can now also return a HttpStatusCodeRange object for their status codes.
  • Emit error diagnostic when multiple properties on a response model have the @statusCode decorator.

`@typespec/json-schema` New Features

  • Disable folding of serialized yaml if line is above 80 characters.

`@typespec/openapi` New Features

  • Remove @typespec/rest as a peerDependency.
  • Support Http Status Code ranges.
  • Breaking Change Changed namespace from OpenAPI to TypeSpec.OpenAPI.

`@typespec/openapi3` New Features

  • Remove @typespec/rest as a peerDependency.
  • Add support for constraints on union types.
  • Add support for @returns and @errors doc comment tags.
  • Add support for http status code ranges.

`@typespec/protobuf` New features

  • Added support for emitting documentation comments in protobuf specifications.

`@typespec/rest` New features

  • Remove peer dependency on @typespec/http library.

Bug Fixes

`@typespec/compiler` Bug Fixes

  • Fix: Correct handling of hyphens in doc comments.
  • Fix: Colorization of line comments extending into the next line.
  • Fix: Crash when using parentheses in directives.
  • Fix: Allow assigning negative and 0 values to float64 types.
  • Fix: Return non-zero format code when tsp formatting fails.
  • Fix: Correct formatting of anonymous unions to exclude leading ':' character.
  • Fix: Unions and Enum members are now formatted following the same rules as model properties. An extra line will be added between members if the member is annotated with a decorator, directive or doc comment.
  • Fix: Correct formatting of comments between a directive or doc and its node.
  • Fix: tsp init was not creating the tspconfig.yaml file for templates that specified it.
  • Fix: tsp init will create a placeholder tspconfig.yaml file for templates that don't specify an explicit one.
  • Fix: tsp init was ignoring the files specified in an init template.
  • Fix: Language Server wasn't loading the tspconfig.yaml correctly resulting in some options being dropped, like the linter configuration.
  • Fix: Allow null to be assigned as a default value.
  • Fix: Using TypeSpec.Xyz namespace shouldn't require the TypeSpec Prefix.

`@typespec/openapi3` Bug Fixes

  • Fix: Correctly generate discriminated union mapping property with multiple visibilities.
  • Fix: Crash when assigning a numeric default to a union.
  • Fix: Using format ssv or pipes in @header produced an invalid OpenAPI3 schema. It will now change the type to string, ignore the format, and emit a warning diagnostic.
  • Fix: Allow using null as a default value.
  • Fix: Allow use of @oneOf on model properties.
  • Fix: Correct representation of CSV format.
  • Fix: Remove incorrect statusCode field for operations with shared routes.

`@typespec/rest` Bug Fixes

  • Fix: @key can now appear on the base model of a resource.

`@typespec/versioning` Bug Fixes

  • Ensure that use of @renamedFrom does not result in duplicate properties on a model.

Breaking Changes

`@typespec/http` Breaking Changes

  • Http responses can now also return a HttpStatusCodeRange object for their status codes. Emitters will need to handle the additional status code content when processing status codes.
  • Emit error diagnostic when multiple properties on a response model have the @statusCode decorator.

`@typespec/openapi` Breaking Changes

  • Changed namespace from OpenAPI to TypeSpec.OpenAPI.

Website Src Content Docs Release Notes Release 2023 11 07 (`website/src/content/docs/release-notes/release-2023-11-07.md`)


title: November 2023 releaseDate: 2023-11-07

:::caution
This release contains breaking changes
:::

New Features

`@typespec/compiler` New Features

  • Add new unixTimestamp32 scalar to standard library.
  • Emitter Framework: Add hook for handling circular references.
  • Emitter Framework: TypeEmitter now supports Tuples.
  • Emit error diagnostic if a model spreads itself within its declaration.
  • Stop showing empty code frame when diagnostic has no location.

`@typespec/json-schema` New Features

  • Add support for TupleLiteral.

`@typespec/openapi3` New Features

  • Support @summary on data types which emit the JSON Schema title property.

`@typespec/playground` New features

  • Add a format button to the playground command bar.
  • Add a notification to the standalone playground when the playground has been saved.
  • Add resizable panes for the editor and output.

Bug Fixes

`@typespec/compiler` Bug Fixes

  • Fix: @doc and jsdoc comments disappear from multi-segment or nested namespaces.
  • Fix: Using @overload could result in incorrect unassignable type errors.
  • Fix: Referencing a template in an alias might cause augment decorators to not be applied on types referenced in the aliased type.
  • Fix: Issues with removing keys from RekeyableMap.
  • Fix: Properties filtered with @withVisibility will have their visibility removed. This prevent visibility from being applied twice and rendering invalid models.

`@typespec/openapi3` Bug Fixes

  • Fix: Stops emitting an error when using @body _: void in operation parameters and treat it as no body.
  • Fix: Using shared routes would, in some cases, result in a duplicate-header error.

Breaking Changes

Typescript types breaking change

The package.json files for all packages have been changed to use the standard types field instead of the legacy typesVersions field to specify types for subpath exports. This means that packages consuming TypeSpec must use the moduleResolution setting with node16 or nodenext in their tsconfig.json.
You might see the following error:

bash
Cannot find module '@typespec/compiler/testing'. Did you mean to set the 'moduleResolution' option to 'nodenext'

This can be resolved by changing the following in tsconfig.json:

diff
-"moduleResolution": "ESNext",
+"moduleResolution": "node16",

Dropped Support for Node version 16

  • All TypeSpec packages now specify node 18 as the minimum version. Consuming packages should no longer use node 16 and update the node specification in the engines section of their package.json file appropriately.

`@typespec/playground` Breaking Changes

  • Styles must be imported seperately with import \"@typespec/playground/styles.css\";.

`@typespec/versioning` Breaking Changes

  • Update targets for @added, @removed, @renamedFrom, @madeOptional and @typeChangedFrom to more clearly communicate where they can be used. Specs that applied these decorators to types that could not be versioned (like namespace) will now receive an error diagnostic.

Website Src Content Docs Release Notes Release 2023 12 06 (`website/src/content/docs/release-notes/release-2023-12-06.md`)


title: December 2023 releaseDate: 2023-12-06

:::caution
This release contains breaking changes
:::

New Features

`@typespec/compiler` New Features

  • New Language Feature Added string template literal in typespec. Single and multi-line strings can be interpolated with ${ and }. Example \\Doc for url ${url} is here: ${location}\\
  • [Emitter Framework]: emitTypeReference function takes an optional reference context that can be used to patch the context for the target.
  • [Emitter Framework]: Added absolute-path as a known format for emitter options which will validate the value passed by the user resolve to an absolute path.
  • [Emitter Framework]: Allow scalar and enum declarations to provide a reference context.
  • [Emitter Framework]: Allow passing a custom context when calling emitType.
  • Linter rules can now supply a full url to documentation.
  • Upgrade formatter to prettier version 3.1.

`@typespec/http` New Features

  • Add diagnostic when a namespace exists with routes, but no namespace is marked with @service.

`@typespec/json-schema` New Features

  • Add support for simple literal default on model properties.
  • Add support for StringTemplate literals.

`@typespec/openapi3` New Features

  • Now emits all properties of unreferenced schemas.
  • Treat union variants as Discriminator.
  • Emitter will now emit all properties of unreferenced schemas.

`@typespec/playground` New features

  • Add a format button to the playground command bar.
  • Add a notification to the standalone playground when the playground has been saved.
  • Enable changing the theme.
  • Add resizable panes for the editor and output
  • Add ability to pass a custom footer and use Footer and FooterItem component to build your own.
  • Add noopener noreferrer to footer external link
  • Move errors and warnings to a dedicated expandable banner at the bottom of the playground.
  • BREAKING CHANGE Styles must be imported seperately with import \"@typespec/playground/styles.css\";
  • Allow standalone playground to show a loading fallback.
  • Upgrade swagger-ui component.

`@typespec/rest` New Features

  • Fix issue with ResourceCreateModel template collecting "update" properties instead of "create" properties.

Bug Fixes

`@typespec/compiler` Bug Fixes

  • [Formatter]: Fix: Preserve parentheses in valueof expressions inside a union or array (e.g. (value of strring) | Model).
  • Fix: Union variants are now assigned to the parent union.

`@typespec/json-schema` Bug Fixes

  • Fix: Enum with a value of 0 would have resulted in type of string and number.
  • Fix: Scalar constraints combine with base scalar constraints.
  • Fix @maxValueExclusive setting minimumExclusive instead of maximumExclusive

`@typespec/openapi3` Bug Fixes

  • Fix: Report diagnostic instead of throwing errors in the case of duplicate ids or unknown scalar.
  • FIX: Handle bytes as a multipart part type correctly and produce type: string, format: binary

`@typespec/playground` Bug Fixes

  • Configure the program viewer to respect the color theme",

`@typespec/versioning` Bug Fixes

  • Fix crash in versioning library`.

Breaking Changes

  • New Language Feature string interpolation to strings in TypeSpec. Strings that use the sequence ${ are now expected to start a reference to an interpolated value. Strings that contain ${ and are not meant for interpolation must now be escaped with a \, like this: \${.

Semantic Changes in OpenApi3 Output

  • Previously, unreferenced Model types (types not used in any operation) were emitted using read visibility, meaning that properties that had only create or update visibility would not be included in the corresponding schema in the @typespec/openapi3 emitter. In this release, all properties of unreferenced Model types will appear in the output.
  • Previously, bytes types that appeared in a multipart/form-data request or response would be marked as a base64 encoded string. In this release, multipart/form-data bytes types appearing in parts with an object type will be treated as a base64-encoded string, while those in non-model parts will appear as raw binary data.

Website Src Content Docs Release Notes Release 2024 01 23 (`website/src/content/docs/release-notes/release-2024-01-23.md`)


title: January 2024 releaseDate: 2024-01-23

:::caution
This release contains breaking changes
:::

New Features

`@typespec/compiler` New Features

  • New Language Feature Added support for named template arguments. Typespec template arguments can now be instantiated both positionally and by name. See this issue for details.
    • All TypeSpec libraries normalized Template parameter names in anticipation of this change.
  • Renamed template parameters in preparation for named template argument instantiation.
  • Added new --template option to tsp init command line action which lets user specify which template to choose from in the template list.
  • Added a new init template for creating a new TypeSpec emitter.
  • Added a new init template for creating a new TypeSpec library.
  • New Language Feature Added the ability to use another template parameter as a constraint, e.g. model Foo<A, B extends A>.
  • Added @encodedName decorator and getEncodedName accessor.
  • Added support for bytes encode to the general encode type.
  • Library declaration: Enabled declaration of State symbols in the library declaration, providing a central place to define state symbols used in your libraries.
  • Added a new helper function to change casing to the init templates.

`@typespec/bundler` New Features

  • Support for the types entry under exports in package definitions (instead of the legacy typesVersions).
    • All TypeSpec libraries are now using this definition style.
  • Add a new cli for typespec-bundler.
  • Expose a bundle manifest with relative importmap, package name, and version.

`@typespec/openapi3` New Features

  • Added support for @encodedName decorator.

`@typespec/playground` New features

  • Added a format button to the playground command bar.
  • Added a notification to the standalone playground when the playground has been saved.
  • Added resizable panes for the editor and output.
  • Added the ability to pass a custom footer and use Footer and FooterItem component to build your own.
  • Added the ability to configure the playground via createBrowserHost if library loading should use system importmap or the es-module-shim library with importmap-shim. This was added due to the lack of support for external source map in browsers.
  • Provided the ability to configure bundling local libraries with the playground or manage the importmap yourself with skipBundleLibraries option.
  • Moved errors and warnings to a dedicated expandable banner at the bottom of the playground.
  • Removed githubIssueUrl link and replaced with a onFileBug callback.
  • Allowed standalone playground to show a loading fallback.

Bug Fixes

`@typespec/compiler` Bug Fixes

  • Fix: Improved performance of the language server, impacting performance of syntax highlighting and other IDE features.
  • Fix: Enabled compiler file resolution for long path names (256+ characters) on windows.
  • Fix: Turned off warning when tsp init is used with a template that does not specify compilerVersion.

`@typespec/http` Bug Fixes

  • Fix: Ensured @path custom name is respected.
  • Fix: Allowed correct path generation when path parameter is not alpha numeric(Either with a different name provided in @path or if the property name is not an identifier)

`@typespec/playground` Bug Fixes

  • Fix: Configured the program viewer to respect the color theme.
  • Fix: Provided css exports to styles.css in addition to style.css. Both are now available for backward compatibility but styles.css is the recommended name.

`@typespec/rest` Bug Fixes

  • Fix: Corrected handling of custom @path name in the building of the route.

`@typespec/versioning` Bug Fixes

Deprecations

`@typespec-compiler` Deprecations

  • Library declaration: Deprecated linter property on $lib in favor of a new $linter variable that can be exported. This was done to discourage circular references caused by referencing linter rules in $lib.

Breaking Changes

Node Version Requirement

  • TypeSpec dropped support for node version 16, as this version of node is no longer generally supported. Minimum node version is 18.

`typespec-playground` Breaking Changes

  • BREAKING CHANGE Styles must be imported seperately with import \"@typespec/playground/styles.css\";.

Website Src Content Docs Release Notes Release 2024 02 06 (`website/src/content/docs/release-notes/release-2024-02-06.md`)


title: February 2024 releaseDate: 2024-02-06

Release of VSCode and Visual Studio extensions

New Features

`@typespec/compiler` New Features

  • Added an optional validation message to the @pattern decorator.
  • Emitter framework: ObjectBuilder will keep track when built using a Placeholder allowing data to be carried over when chaining ObjectBuilder

`@typespec/openapi3` New Features

  • Added support for @encodedName decorator.

Bug Fixes

`@typespec/compiler` Bug Fixes

  • Template instantiated with ErrorType will get the arg changed to unknown
  • Fix: Error out when using properties in array model

`@typespec/http` Bug Fixes

  • Fix: OpenIDConnect types not exposed on the TypeScript types

`@typespec/openapi3` Bug Fixes

  • Fix issues with nullable properties used in a cycle being wrapped in allOf when not needed
  • Fix circular reference would not always inline array properties
  • Add support for OpenIdConnect auth scheme

Website Src Content Docs Release Notes Release 2024 03 05 (`website/src/content/docs/release-notes/release-2024-03-05.md`)


title: March 2024 releaseDate: 2024-03-05

:::caution
This release contains breaking changes and deprecations
:::

Breaking Changes

`@typespec/compiler`

  • #2920 Intersecting Record<T> with incompatible properties will now emit an error

`@typespec/prettier-plugin-typespec`

  • #2956 Drop support for prettier 2.0

Deprecations

`@typespec/compiler`

  • #2919 [API] Create a new export @typespec/compiler/utils exports. Deprecate export from @typespec/compiler of utils like DuplicateTracker, Queue, createTwoKeyMap, etc.
  • #2902 Deprecate @service version property. If wanting to describe a service versioning you can use the @typespec/versioning library. If wanting to describe the project version you can use the package.json version. For OpenAPI generation. the @OpenAPI.info nows decorator allows providing the document version.

Features

`@typespec/compiler`

  • #2888 Add support for codefixes
  • #2920 Add support for ...Record<T> to define the type of remaining properties
  • #2968 Any subtype of an error(marked with @error) is now an error.

`@typespec/http`

  • #2901 Add ability to specify authentication and different scopes per operation
  • #2958 Validate that only one @useAuth decorator is applied to a type.

`@typespec/openapi`

  • #2902 Add support for all properties of openapi info object on the @info decorator

`@typespec/openapi3`

  • #2902 Add support for all properties of openapi info object on the @info decorator
  • #2950 Add getOpenAPI3 function that takes a TypeSpec program and returns the emitted OpenAPI as an object. Useful for other emitters and tools that want to work with emitted OpenAPI directly without writing it to disk.
  • #2933 Add a new option safeint-strategy that can be set to double-int to emit type: integer, format: double-int instead of type: integer, format: int64 when using the safeint scalar.

`@typespec/playground`

  • #2936 Autocomplete installed libraries in import statements

Bug Fixes

`@typespec/compiler`

  • #2932 Report error when having a circular template constraint e.g. model Example<T extends T>
  • #2955 [Formatter] Formatting file with only comments would reorder the first line.
  • #2934 [IDE] Fix issue when deleting an open file outside the IDE that would crash the language server
  • #2959 Decorators that have missing arguments will not run. This is inline with passing invalid argument to a decorator that would prevent it from running.
  • #2976 [IDE] Fix type documentation shown twice when hovering symbols or in completion details.
  • #2929 [API] Add Scalar to TS TemplatedType type
  • #2978 [IDE] Go to imports
  • #2936 [IDE] Autocompleting file or folder with non alpha numeric character completes correctly
  • #2936 [IDE] Fix crashing when trying to autocomplete an invalid folder
  • #2951 Wrap string in quotes in errors
  • #2886 Fix: tsp compile --watch was missing coloring and error previews

`@typespec/http`

  • #2948 Fix don't emit shared route error when verb don't match

`@typespec/json-schema`

  • #2977 Respect compiler noEmit flag

Website Src Content Docs Release Notes Typespec 0 55 (`website/src/content/docs/release-notes/typespec-0-55.md`)


slug: release-notes/typespec-0-55 title: 0.55 - April 2024 releaseDate: 2024-04-02 version: "0.55"

:::caution
This release contains deprecations
:::

Deprecations

@typespec/compiler

  • #2910 Deprecate @projectedName decorator. @encodedName should be used instead.

    Example:

    diff
    -@projectedName("json", "exp")
    +@encodedName("application/json", "exp")
  • #3094 Deprecate @knownValues decorator. Use a named union of string literal with a string variant to achieve the same result without a decorator

    Example:

    diff
    -enum FooKV { a, b, c}
    -@knownValues(FooKV)
    -scalar foo extends string;
    +union Foo { "a", "b", "c", string }

Features

@typespec/compiler

  • #2992 Enable the use of @encode for model properties that have a union type. This supports cases like @encode("rfc3339") prop: utcDateTime | null

@typespec/versioning

  • #3053 Add support for versioning of scalars(Added, removed, renamed)

Bug Fixes

@typespec/compiler

  • #3018 Grammar: Fix comments in template params not tokenized
  • #3052 Fix: Union template declaration were incorrectly being finished in projection
  • #2983 Warnings converted to error with warn-as-error do not prevent compilation from moving to the next stage like regular warnings
  • #3041 Improve reliability of application of codefixes in IDE, often it would not do anything
  • #3069 TmLanguage: Fix tokenization of escaped identifiers, enums and unions

@typespec/openapi3

  • #3077 Do not crash if using an unsupported intrinsic type
  • #2967 Fix crash: emit {nullable: true} when trying to emit null in openapi3
  • #3013 Fix: OpenAPI3 not marking part of bytes or something else as format: binary
  • #3090 Fix: Literal unions with the same variants keep adding duplicate entries
  • #3049 Fix visibility naming conflict when a model used with extends was used in different visibility.

@typespec/eslint-config-typespec

  • #2122 Ignore generated-defs folder

Website Src Content Docs Release Notes Typespec 0 56 (`website/src/content/docs/release-notes/typespec-0-56.md`)


slug: release-notes/typespec-0-56 title: 0.56 - May 2024 releaseDate: 2024-05-07 version: "0.56"

:::caution
This release contains breaking changes
:::

Breaking Changes

@typespec/http

  • #2945 Empty model after removing metadata and applying visibility always results in "void"
    This means the following case have changed from returning {} to no body

    tsp
    op b1(): {};
    op b2(): {
      @visibility("none") prop: string;
    };
    op b3(): {
      @added(Versions.v2) prop: string;
    };

    Workaround: Use explicit @body

    tsp
    op b1(): {
      @body _: {};
    };
    op b2(): {
      @body _: {
        @visibility("none") prop: string;
      };
    };
    op b3(): {
      @body _: {
        @added(Versions.v2) prop: string;
      };
    };
  • #2945 Implicit status code always 200 except if response is explicitly void

    tsp
    op c1(): {
      @header foo: string;
    }; // status code 200 (used to be 204)

    Solution: Add explicit @statusCode

    tsp
    op c1(): {
      @header foo: string;
      @statusCode _: 204;
    };
    op c1(): {
      @header foo: string;
      ...NoContent;
    }; // or spread common model
  • #2945 @body means this is the body

    This change makes using @body mean this is the exact body and everything underneath will be included, including metadata properties. If metadata properties are present on the body, a warning will be logged.

    tsp
    op a1(): {@body _: {@header foo: string, other: string} };
                    ^ warning header in a body, it will not be included as a header.

    Use @bodyRoot if you want to only change where to resolve the body from.

    tsp
    op a1(): {
      @bodyRoot _: {
        @header foo: string;
        other: string;
      };
    };
  • #2945 Properties are not automatically omitted if everything was removed from metadata or visibility

    tsp
    op d1(): {
      headers: {
        @header foo: string;
      };
    }; // body will be {headers: {}}

    Solution: use @bodyIgnore

    tsp
    op d1(): {
      @bodyIgnore headers: {
        @header foo: string;
      };
    }; // body will be {headers: {}}

Features

@typespec/compiler

  • #3035 getEncode returns the fully qualified enum member name if using a custom enum.
  • #3183 Show template parameters when hovering on an operation template
  • #3191 [API] Add new sourceModels property to model

@typespec/rest

  • #2945 Add support for new @bodyRoot and @body distinction

@typespec/openapi3

  • #2945 Add support for new @bodyRoot and @body distinction

@typespec/html-program-viewer

  • #3191 Add sourceModels property to model view

Bug Fixes

@typespec/compiler

  • #3170 --nostdlib flag will now work by only applying to optional standard library types
  • #3212 Fix: augmenting template model property could result in sending invalid argument to decorator
  • #3188 Fix: Do not crash when trying to access member of aliased expressions
  • #3185 Fix tsp init hanging when done due to unclosed connection
  • #3151 IDE: Fix completion of statement keywords
  • #3287 Templated interface extending another templated interface shouldn't run decorator on their operations
  • #3290 Model with an optional property should not satisfy a constraint with that property required. ({foo?: string} cannot be assigned to a constraint of {foo: string})
  • #3163 Fix: Model with spread indexer shouldn't validate explicit properties
  • #3227 Stop running decorators on partially instantiated operations(When interface is instantiated but not the operation)
  • #3180 Fix issue where directives were not parsed to the leaf node in multi-segment Namespace segments.
  • #3243 Fix calling tsp install on windows due to recent NodeJS breaking change to fix vulnerability.
  • #3216 Fix compiler crash when using an invalid is target in an interface operation template
  • #3246 Internals: Use node built-in fetch API that is now stable since node 18.13.0

@typespec/http

  • #3196 Fix password flow defining authorizationUrl instead of tokenUrl
  • #3190 Fix @path param mapping when spreading a record in operation parameters
  • #3218 Fix: @path property shouldn't be applicableMetadata if the visibility contains Read

@typespec/versioning

  • #3264 Fix crash when @service appears inside a versioned namespace

@typespec/openapi3

  • #3218 Fix: @path property should be included in unreachable models

Website Src Content Docs Release Notes Typespec 0 57 (`website/src/content/docs/release-notes/typespec-0-57.md`)


slug: release-notes/typespec-0-57 title: 0.57 - June 2024 releaseDate: 2024-06-10 version: "0.57"

:::caution
This release contains deprecations and breaking changes
:::

Values In TypeSpec

Decorators often expect some sort of configuration to be passed as an argument. In version 0.51.0 we introduced the valueof keyword to constrain a decorator or template parameter to be a value, however this was limited to string, numeric and boolean values.
When dealing with more complex decorators or free form data, one had to use a model type to represent the data. Unfortunately, this approach was difficult for decorator authors to handle reliably and required somewhat sophisticated validation to avoid some common pitfalls that couldn't be prevented through type system constraints.

In this release we introduce a new concept to TypeSpec: values. This includes new syntax for declaring object and array values, scalar constructors, and const declarations.

Object values

Object values can be declared with the #{} syntax.

tsp
const user = #{ name: "Bob", age: 48, address: #{ city: "London" } };

Array values

Array values can be declared with the #[] syntax.

tsp
const users = #["Bob", "Frank"];

`const`

Const declares a variable that holds a value, and can optionally specify a type.

tsp
const name = "Bob";
const name: string = "Bob";

Scalar constructors

Scalars can define custom values with scalar value constructors. Scalars which don't extend a base type like string, number or boolean can define a constructor.

tsp
scalar ipV4 {
  init fromInt(value: int32);
}
const ip: ipV4 = ipV4.fromInt(3232235776);

See values doc for more info

Breaking changes

  • [#3022