TypeScript discriminated unions over two properties instead of one?

36 views Asked by At

Is it possible to do discriminated unions in TypeScript over two properties instead of one? Here is a TypeScript playground:

type BrowserRemote = {
    remote: true
    foo: string
}

type BrowserLocal = {
    bar: string
}

type NodeRemote = {
    remote: true
    baz: string
}

type NodeClientLocal = {
    client: true
    bing: string
}

type NodeClientRemote = {
    boom: string
}

type NodeType = NodeRemote | NodeClientLocal | NodeClientRemote

type BrowserType = BrowserRemote | BrowserLocal

function convertNode(input: NodeType) {
    console.log(input)
}

function convertBrowser(input: BrowserType) {
    console.log(input)
}

convertNode({
    remote: true,
    baz: 'foo'
})

convertNode({
    remote: true,
    boom: 'foo'
})

Basically, I have two functions convertNode and convertBrowser, one for Node.js and one for the browser. The Node.js function can take 3 input styles depending on if it's making a remote request (connecting to a REST API internally), or making a local request (remote is undefined). The local request can be either coming from a client or not coming from a client (client is undefined). The browser can be either remote or not remote, so it's a simpler version than Node.js. So let's focus on Node.js.

The "discriminated union" property really is a combination of 2 properties:

  • remote: true, client: undefined | false
  • remote: undefined | false, client: true
  • remote: undefined | false, client: undefined | false

But I would like to not have to pass in client: undefined or remote: undefined into my function calls. Likewise, I would like the API to stay like this, with 2 properties instead of 1 property. 1 property might look like this:

  • source: 'remote'
  • source: 'local:client'
  • source: 'local'

I may go with that approach if absolutely necessary, but I wanted to see if this was possible somehow to do discriminated unions on the 2 properties, to get that TypeScript playground to work. Is it possible? If not, why not? If so, how could you get it to work?

Note: Each permutation will have some shared properties and some unique properties. So remote/local:client/local will each have some similar properties, and some different properties. In addition, they might have a property named the same thing, but a different type, like this:

type NodeLocalClient = {
  client: true
  file: ArrayBuffer
}

type NodeLocal = {
  file: string
}

Granted, I could really try to have the props of different types have different names, but it may not always be desirable to try and enforce that.

0

There are 0 answers