Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions eng/scripts/doc-updater/knowledge/tcgc.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,26 @@ namespace (@clientNamespace), naming (@clientName), overload, structure (@client
## @clientLocation + scoped @client validation (July 2026)

- Bug fix in `src/validations/types.ts`: `@clientLocation` name-collision validation now skips operations that belong to an explicit `@client` scoped to a _different_ language than the scope being validated (`isClientForOtherScopeOnly`). Prevents false-positive collisions for `is`-derived operations inside a `@client(..., "java")` interface. Internal validation only — no user-facing doc change.

## SdkClientType.versionsEnum (July 2026)

- `SdkClientType` gained `versionsEnum?: SdkEnumType` (`src/interfaces.ts`, populated by `getVersionsEnum` in `src/clients.ts`). It is the API-versions enum for the client's service, `usage` includes `UsageFlags.ApiVersionEnum` (8), and it is the SAME object instance that appears in `SdkPackage.enums`.
- `undefined` for unversioned services and for multi-service root clients (spanning >1 service). Sub-clients that map to a single service still get their own service's enum. Verified by `test/package/versioning.test.ts` ("client has versionsEnum reference", "multi-service client has no versionsEnum").
- Context caches these per-service via `__serviceToVersionsSdkEnum` and exposes `getPackageVersionSdkEnum(): Map<Namespace, SdkEnumType>` (emitter helper on `TCGCContext`/`SdkContext`).
- Documented in guideline.md "Client" section. Emitter-consumed type-graph metadata — no Spector spec needed.

## SSE Metadata (July 2026)

- `SdkBodyParameter` and `SdkMethodResponse` gained `sseMetadata?: SdkSseMetadata`, set ALONGSIDE `streamMetadata` when the body/response is a server-sent event stream (`text/event-stream`, `SSEStream`). `undefined` for non-event streams like JSONL. Built by `buildSdkSseMetadata` in `src/http.ts`.
- `SdkSseMetadata.events` is `SdkSseEventMetadata[]`, one entry per variant of the streamed `@events` union. Derived from `@typespec/events` event definitions plus the `@typespec/sse` `@terminalEvent` marker. Fields: `eventType?` (SSE `event:` name from named variant; undefined→`message` event), `isTerminalEvent`, `isEventEnvelope`, `type`/`contentType`, `payloadType`/`payloadContentType`. When `isEventEnvelope` is false, `type`==`payloadType` and content types match.
- Kept separate from `SdkStreamMetadata` because SSE, streaming, and events are modeled by three distinct TypeSpec libraries (`@typespec/sse`, `@typespec/http`, `@typespec/events`).
- Documented in guideline.md "Operation" section under a new "Streaming and Server-Sent Events" subsection (also introduced `streamMetadata` documentation there, which was previously undocumented). Tests: `test/methods/sse.test.ts`, `test/methods/streams.test.ts`. Emitter type-graph metadata — no Spector spec needed.

## no-unnamed-types Linter Rule REMOVED (July 2026)

- The `no-unnamed-types` rule was removed from `src/linter.ts` (both the rules array and `no-unnamed-types.rule.ts` / `.md` deleted). `reference/linter.md` no longer lists it — already consistent. Do NOT re-add it.
- Rule source files were renamed to the `<name>.rule.ts` convention (e.g. `property-name-conflict.ts` → `property-name-conflict.rule.ts`); `csharp-no-url-suffix` still uses `.ts`.

## Diagnostic Messages Externalized (July 2026)

- Diagnostic message definitions were moved out of `src/lib.ts` into individual `src/diagnostics/<name>.md` files (loaded at build). Purely an authoring refactor; reference docs regenerate the same content. No user-facing doc action.
4 changes: 2 additions & 2 deletions eng/scripts/doc-updater/knowledge/tcgc.meta.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"lastCommit": "5036a37233b303fd4c59afc2c6c8ffc6d6194e3a",
"lastUpdated": "2026-07-20T10:09:56.076Z",
"lastCommit": "51207aabe222d09901be75e5c7cd3bcb10464f51",
"lastUpdated": "2026-07-27T10:27:10.237Z",
"analyzedPaths": [
"packages/typespec-client-generator-core/src",
"packages/typespec-client-generator-core/lib",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ llmstxt: true
import { ClientTabs, ClientTabItem } from "@components/client-tabs";

This page documents the default generation logic for client's basic method as well as how to customize it.
For advanced paging, long-running or multipart operations, see the [Paging Operations](../05pagingoperations/), [Long-Running Operations](../06longrunningoperations/) and [Multipart Operations](../07multipart/) pages.
For advanced paging, long-running, multipart or streaming operations, see the [Paging Operations](../05pagingoperations/), [Long-Running Operations](../06longrunningoperations/), [Multipart Operations](../07multipart/) and [Streaming Operations](../13streaming/) pages.

## Convenience and protocol methods

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
---
title: Streaming Operations
llmstxt: true
---

import { ClientTabs, ClientTabItem } from "@components/client-tabs";

This doc explains how to author TypeSpec streaming operations and what client emitters will generate for them.

TypeSpec supports three kinds of streaming bodies via the `@typespec/streams`, `@typespec/sse`, and `@typespec/events` packages:

- **`JsonlStream<T>`** — a newline-delimited JSON stream where every line is a JSON-encoded `T`.
- **`SSEStream<TEvents>`** — a server-sent event stream (`text/event-stream`) carrying heterogeneous events described by an `@events` union.
- **`HttpStream<T, ContentType>`** — a generic streaming body; using `ContentType = "text/event-stream"` with an `@events` union activates the same SSE metadata as `SSEStream`.

:::note
Import the required packages in your TypeSpec spec:

```tsp
import "@typespec/streams";
import "@typespec/sse";
import "@typespec/events";
```
:::

## JSONL streaming response

Use `JsonlStream<T>` when your service responds with newline-delimited JSON lines, each encoding a value of type `T`.

<ClientTabs>

```typespec
import "@typespec/streams";

using TypeSpec.Streams;

model Chunk {
text: string;
}

@post
op generate(@body request: { prompt: string }): JsonlStream<Chunk>;
```

```python
# unsupported
```

```csharp
// unsupported
```

```typescript
// unsupported
```

```java
// unsupported
```

```go
// unsupported
```

</ClientTabs>

## SSE streaming response

Use `SSEStream<TEvents>` to define a server-sent event stream. Events are described by a union decorated with `@Events.events`. Each named variant becomes an SSE event type; unnamed variants produce `message` events.

<ClientTabs>

```typespec
import "@typespec/sse";
import "@typespec/events";

using TypeSpec.SSE;
using TypeSpec.Events;

model ResponseCreated {
id: string;
}

model ResponseDelta {
delta: string;
}

@events
union ResponseEvents {
@contentType("application/json")
responseCreated: ResponseCreated,

@contentType("application/json")
responseDelta: ResponseDelta,

@contentType("text/plain")
@terminalEvent
"[DONE]",
}

@post
op stream(@body request: { prompt: string }): SSEStream<ResponseEvents>;
```

```python
# unsupported
```

```csharp
// unsupported
```

```typescript
// unsupported
```

```java
// unsupported
```

```go
// unsupported
```

</ClientTabs>

### Terminal events

Mark a variant with `@terminalEvent` to indicate that receiving this event signals the end of the stream. Client emitters use this to know when to stop reading and close the connection.

```typespec
import "@typespec/sse";
import "@typespec/events";

using TypeSpec.SSE;
using TypeSpec.Events;

model DeltaEvent {
delta: string;
}

@events
union ChatEvents {
@contentType("application/json")
delta: DeltaEvent,

@contentType("text/plain")
@terminalEvent
"[DONE]",
}

@post
op chat(@body request: { message: string }): SSEStream<ChatEvents>;
```

### Event envelopes

When your SSE event wraps a separate payload, use `@Events.data` on the payload property to distinguish the envelope from its content. Emitters expose both the envelope type and the unwrapped payload type through `sseMetadata`.

```typespec
import "@typespec/sse";
import "@typespec/events";

using TypeSpec.SSE;
using TypeSpec.Events;

model ChatMessage {
role: string;
content: string;
}

@events
union ChatEvents {
@contentType("application/json")
message: { done: false, @data message: ChatMessage },

@contentType("text/plain")
@terminalEvent
done: { done: true },
}

@post
op chat(@body request: { message: string }): SSEStream<ChatEvents>;
```

### Unnamed (message) events

A variant without a name in the union produces an SSE `message` event — the `event:` field is omitted on the wire. Use this when your service emits a single homogeneous event type.

```typespec
import "@typespec/sse";
import "@typespec/events";

using TypeSpec.SSE;
using TypeSpec.Events;

model Info {
desc: string;
}

@events
union BasicEvents {
@contentType("application/json")
Info,
}

op receive(): SSEStream<BasicEvents>;
```

## SSE streaming request

`SSEStream` can also be used as a request body when the client sends an event stream to the service.

```typespec
import "@typespec/sse";
import "@typespec/events";

using TypeSpec.SSE;
using TypeSpec.Events;

model InputEvent {
data: string;
}

@events
union InputEvents {
@contentType("application/json")
inputEvent: InputEvent,
}

op send(stream: SSEStream<InputEvents>): void;
```

## Custom streaming body

For content types other than JSONL or `text/event-stream`, define a model with `@streamOf` and a `@body bytes` property.

```typespec
import "@typespec/streams";

using TypeSpec.Streams;

model AudioChunk { id: string; }

@streamOf(AudioChunk)
model AudioStream {
@header contentType: "audio/mpeg";
@body body: bytes;
}

op synthesize(@body request: { text: string }): AudioStream;
```
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ export async function $onEmit(context: EmitContext<SdkEmitterOptions>) {

Emitters can get first-level clients of a client package from `SdkPackage.clients`. An [`SdkClientType`](../reference/js-api/interfaces/sdkclienttype/) represents a client in the package. Emitters can use `SdkClientType.children` to get nested sub clients, and use `SdkClientType.parent` to trace back.

`SdkClientType.versionsEnum` is the [`SdkEnumType`](../reference/js-api/interfaces/sdkenumtype/) describing the API versions supported by this client's service (its `usage` includes the `ApiVersionEnum` flag, and it is the same object that appears in `SdkPackage.enums`). It is `undefined` for unversioned services and for multi-service root clients (which span more than one service). Sub clients that map to a single service still expose their own service's `versionsEnum`.

`SdkClientType.clientInitialization` tells emitters how to initialize the client. [`SdkClientInitializationType`](../reference/js-api/interfaces/sdkclientinitializationtype/) contains info about the client's initialization parameters and how the client can be initialized, controlled by the `initializedBy` flags:

- `Individually` (1): The client can be instantiated directly by the user.
Expand Down Expand Up @@ -187,6 +189,18 @@ TCGC currently supports one kind of operation: [`SdkHttpOperation`](../reference

Each parameter for an HTTP operation has a `methodParameterSegments` property to indicate the mapping of one payload parameter with the path of one or more method-level parameters or model properties. This helps emitters determine how to compose the underlying payload with the method's parameters. One body parameter can have several method-level parameter or model property mapping paths because of the implicit body parameter resolving from the TypeSpec HTTP library.

#### Streaming and Server-Sent Events

When the request body or a response is a streaming type, TCGC sets `streamMetadata` (an [`SdkStreamMetadata`](../reference/js-api/interfaces/sdkstreammetadata/)) on the corresponding `SdkBodyParameter` or `SdkMethodResponse`, describing the stream's content types.

For server-sent event (SSE, `text/event-stream`) streams specifically, TCGC additionally sets `sseMetadata` (an [`SdkSseMetadata`](../reference/js-api/interfaces/sdkssemetadata/)) alongside `streamMetadata`. It is `undefined` for non-event streams such as JSONL. `SdkSseMetadata.events` has one [`SdkSseEventMetadata`](../reference/js-api/interfaces/sdksseeventmetadata/) entry per variant of the streamed `@events` union, giving emitters everything they need to (de)serialize each event without re-deriving it from raw TypeSpec:

- `eventType`: the SSE `event:` field name (from the named union variant); `undefined` for unnamed variants, which are `message` events with no `event:` field.
- `isTerminalEvent`: whether receiving this event terminates the stream (from `@terminalEvent`), so the client should disconnect.
- `isEventEnvelope`: whether `type` describes an envelope wrapping a separate `@data` payload. When `false`, `type`/`payloadType` (and their content types) are identical.
- `type` / `contentType`: the event type and its content type (the envelope when `isEventEnvelope` is `true`).
- `payloadType` / `payloadContentType`: the event payload type and its content type.

### Type

For types in TypeSpec, TCGC provides several client types to represent them in a way that's more similar to client languages.
Expand Down
Loading