Skip to content

Commit

Permalink
Document type helpers for oneOfs (#1023)
Browse files Browse the repository at this point in the history
Add a section to the documentation that shows some TypeScript types that
could be useful in working with union-style oneOfs. I've written these a
few times in different projects so I could properly type helper
functions that operate on generated oneOfs, and I figured it might be
useful to other users of ts-proto.

As an example, given the following:

```ts
interface MusicPlayerRequest {
  command?:
    | { $case: "start"; start: StartCommand }
    | { $case: "stop"; stop: StopCommand }
    | { $case: "next"; list: NextCommand }
    | undefined; 
}
```

Then the helpers can be used like this:

```ts
type MusicPlayerCommandNames = OneOfCases<MusicPlayerRequest['command']>; // = "start" | "stop" | "next"

type MusicPlayerCommands = OneOfValues<MusicPlayerRequest['command']>; // = StartCommand | StopCommand | NextCommand

type NextCommandByName = OneOfCase<MusicPlayerRequest['command'], 'next'> // = NextCommand

function sendCommand<C extends MusicPlayerCommandNames>(
  commandName: MusicPlayerCommandNames, 
  command: OneOfCase<MusicPlayerRequest['command'], C>
) {
  ...
}

// The `command` argument is automatically of type NextCommand because the `commandName` argument is "next"
sendCommand("next", { skip: 1 }) 
```
  • Loading branch information
bhollis authored Mar 29, 2024
1 parent 25b3da0 commit 9727bba
Showing 1 changed file with 22 additions and 0 deletions.
22 changes: 22 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,28 @@ As this will automatically enforce only one of `field_a` or `field_b` "being set

In ts-proto's currently-unscheduled 2.x release, `oneof=unions` will become the default behavior.

## OneOf Type Helpers

The following helper types may make it easier to work with the types generated from `oneof=unions`:

```ts
/** Extracts all the case names from a oneOf field. */
type OneOfCases<T> = T extends { $case: infer U extends string } ? U : never;

/** Extracts a union of all the value types from a oneOf field */
type OneOfValues<T> = T extends { $case: infer U extends string; [key: string]: unknown }
? T[U]
: never;

/** Extracts the specific type of a oneOf case based on its field name */
type OneOfCase<T, K extends OneOfCases<T>> = T extends {
$case: infer U extends K;
[key: string]: unknown;
}
? T[U]
: never;
```

# Default values and unset fields

In core Protobuf (and so also `ts-proto`), values that are _unset_ or equal to the default value are not sent over the wire.
Expand Down

0 comments on commit 9727bba

Please sign in to comment.