A
arktype•3mo ago
JameEnder

Automatic "full" discriminated union

Hello, I have this code:
const ActorInputSchema = type({
runMode: '"actor"',
actorName: 'string',
actorInput: 'unknown',
'datasetId?': 'never',
'exactData?': 'never',
})

const DatasetInputSchema = type({
runMode: '"dataset"',
'actorName?': 'never',
'actorInput?': 'never',
datasetId: 'string',
'exactData?': 'never',
})

const ExactInputSchema = type({
runMode: '"exact"',
'actorName?': 'never',
'actorInput?': 'never',
'datasetId?': 'never',
exactData: 'string'
})

const InputSchema = ActorInputSchema.or(DatasetInputSchema).or(ExactInputSchema)

type Input = typeof InputSchema.infer;

const input = InputSchema(/* some data */);

if (input instanceof type.errors) {
console.error(input.message); process.exit();
}

const {
runMode,
// Property actorName does not exist on type...
actorName,
actorInput,
datasetId,
exactData,
} = input as Input;
const ActorInputSchema = type({
runMode: '"actor"',
actorName: 'string',
actorInput: 'unknown',
'datasetId?': 'never',
'exactData?': 'never',
})

const DatasetInputSchema = type({
runMode: '"dataset"',
'actorName?': 'never',
'actorInput?': 'never',
datasetId: 'string',
'exactData?': 'never',
})

const ExactInputSchema = type({
runMode: '"exact"',
'actorName?': 'never',
'actorInput?': 'never',
'datasetId?': 'never',
exactData: 'string'
})

const InputSchema = ActorInputSchema.or(DatasetInputSchema).or(ExactInputSchema)

type Input = typeof InputSchema.infer;

const input = InputSchema(/* some data */);

if (input instanceof type.errors) {
console.error(input.message); process.exit();
}

const {
runMode,
// Property actorName does not exist on type...
actorName,
actorInput,
datasetId,
exactData,
} = input as Input;
But in this case, I have to add field?: 'never' to make the compiler happy when destructuring the validated input, else it says that some field doesn't exist on it. Is there a way to make this work, or is my approach wrong from the start? Thank you! :D
13 Replies
Dimava
Dimava•3mo ago
First of all, there is '+': 'reject' aka .onUnknownKey('reject')
JameEnder
JameEnderOP•3mo ago
Tried both to add the + field to each definition, or apply the onUnknownKey to the final Input, and both dont fix the issue
Dimava
Dimava•3mo ago
Nah that's another stuff, just telling about it
JameEnder
JameEnderOP•3mo ago
That's useful, just not for solving this specific issue :D
Dimava
Dimava•2mo ago
David, wait you don't have xor type?
import { show } from '@ark/util'
import { type, Type, scope } from 'arktype'


const ActorInputSchema = type({
runMode: '"actor"',
actorName: 'string',
actorInput: 'unknown',
})

const DatasetInputSchema = type({
runMode: '"dataset"',
datasetId: 'string',
})

const ExactInputSchema = type({
runMode: '"exact"',
exactData: 'string'
})

type allKeysInScope<$> = {
[k in keyof $]: keyof $[k]
}[keyof $];

type orFromScope<$> = {
[k in keyof $]: $[k] & { [p in Exclude<allKeysInScope<$>, keyof $[k]>]?: never }
}[keyof $];

function oneOf<const defs>(
defs: scope.validate<defs>
): Type<show<orFromScope<scope.infer<defs>>>> {
let s = scope({ ...defs, _result: Object.keys(defs).join('|') as any })
return s.export()._result.onUndeclaredKey("reject") as any
}

let T = oneOf({
ActorInputSchema,
DatasetInputSchema,
ExactInputSchema,
})

let v = T.assert({})
import { show } from '@ark/util'
import { type, Type, scope } from 'arktype'


const ActorInputSchema = type({
runMode: '"actor"',
actorName: 'string',
actorInput: 'unknown',
})

const DatasetInputSchema = type({
runMode: '"dataset"',
datasetId: 'string',
})

const ExactInputSchema = type({
runMode: '"exact"',
exactData: 'string'
})

type allKeysInScope<$> = {
[k in keyof $]: keyof $[k]
}[keyof $];

type orFromScope<$> = {
[k in keyof $]: $[k] & { [p in Exclude<allKeysInScope<$>, keyof $[k]>]?: never }
}[keyof $];

function oneOf<const defs>(
defs: scope.validate<defs>
): Type<show<orFromScope<scope.infer<defs>>>> {
let s = scope({ ...defs, _result: Object.keys(defs).join('|') as any })
return s.export()._result.onUndeclaredKey("reject") as any
}

let T = oneOf({
ActorInputSchema,
DatasetInputSchema,
ExactInputSchema,
})

let v = T.assert({})
@JameEnder
JameEnder
JameEnderOP•2mo ago
That seems like a working solution, yet i wanted to reduce the complexity, not the other way around :D It seems weird that the library can't do it on its own
Dimava
Dimava•2mo ago
Yes, I've told David we gonna need this stuff in #Dynamic type based on another value So you are the perfect example of someone who actually need this to prove my point
JameEnder
JameEnderOP•2mo ago
Neat
Dimava
Dimava•2mo ago
Remind me to ping hit tomorrow ( I try not to ping him on weekends idk why )
TizzySaurus
TizzySaurus•2mo ago
What you're trying to do here with the destructing is quite clearly wrong from a TS perspective, so the error is correct You'd need to narrow on runMode before you can access exactData/datasetId/...
const InputSchema = ActorInputSchema.or(...).or(...);

const input = InputSchema.assert(/* some data */); // .assert() will automatically throw when data isn't compliant to schema

if (input.runMode === "actor") {
// type of `input` is automatically narrowed to that of ActorInputSchema
const { actorName, actorInput } = input;
...
} else if (input.runMode === "dataset") {
// type of `input` is automatically narrowed to that of DatasetInputSchema
const { datasetId } = input;
...
} else {
// type of `input` is automatically narrowed to that of ExactInputSchema
const { exactData } = input;
...
}
const InputSchema = ActorInputSchema.or(...).or(...);

const input = InputSchema.assert(/* some data */); // .assert() will automatically throw when data isn't compliant to schema

if (input.runMode === "actor") {
// type of `input` is automatically narrowed to that of ActorInputSchema
const { actorName, actorInput } = input;
...
} else if (input.runMode === "dataset") {
// type of `input` is automatically narrowed to that of DatasetInputSchema
const { datasetId } = input;
...
} else {
// type of `input` is automatically narrowed to that of ExactInputSchema
const { exactData } = input;
...
}
That's the correct way to use discriminated unions @JameEnder You don't need any of the complexity that @Dimava the Monoreaper gave for this
Dimava
Dimava•2mo ago
@JameEnder your opinion? 🤔 does TS narrows on checks on deconstructed stuff?
JameEnder
JameEnderOP•2mo ago
I'll test it out later today and let you know
TizzySaurus
TizzySaurus•2mo ago
Unfortunately not. So you have to do the check on input.runMode and not a deconstructed runMode Or at least when I tried it a few months ago that was the case
Want results from more Discord servers?
Add your server