A
arktype5mo ago
PIat

Extract type from or

Hello! Is it possible, using the following schema:
const userType = type({
intent: "'profile'",
name: 'string>0',
surname: 'string>4',
})
.or({
intent: "'private'",
password: 'string>0',
})
const userType = type({
intent: "'profile'",
name: 'string>0',
surname: 'string>4',
})
.or({
intent: "'private'",
password: 'string>0',
})
to extract one of the types ("find" it by the intent field)? I'd like to use it for multiple forms on a page. It's possible to split it into multiple types, but having it in one type is more readable to me
17 Replies
ssalbdivad
ssalbdivad5mo ago
Yeah I'd say ideally you'd just want to define it as two types individually and then .or them together (usually best to start by defining any components you need to reuse rather than extract them out). That said, there is an API just like TS for this:
userType.extract({ intent: "'profile'" })
userType.extract({ intent: "'profile'" })
PIat
PIatOP5mo ago
Omg!!!! Perfect, thanks! I won't be reusing it at all, that's why I like to keep it as a single object
PIat
PIatOP5mo ago
Where could I see an example of building such a helper in a type-safe way? (extracting the type by intent and omitting the intent key) Some test maybe?
const formType = type({
intent: "'profile'",
name: 'string>0',
surname: 'string>4',
}).or({
intent: "'private'",
password: 'string>0',
})

type ExtractIntents<T> = T extends { intent: infer I } ? I : never

function getFormType<T extends Type, I extends ExtractIntents<inferAmbient<T>>>(
type: T,
intent: I,
) {
return type.extract({ intent: `'${intent}'` }).omit('intent')
}
const formType = type({
intent: "'profile'",
name: 'string>0',
surname: 'string>4',
}).or({
intent: "'private'",
password: 'string>0',
})

type ExtractIntents<T> = T extends { intent: infer I } ? I : never

function getFormType<T extends Type, I extends ExtractIntents<inferAmbient<T>>>(
type: T,
intent: I,
) {
return type.extract({ intent: `'${intent}'` }).omit('intent')
}
My abominations end up with never
type step1 = Type<inferAmbient<typeof formType>>

type step2 = Extract<step1, { intent: "'private'"}>
type step1 = Type<inferAmbient<typeof formType>>

type step2 = Extract<step1, { intent: "'private'"}>
No description
PIat
PIatOP5mo ago
type ExtractIntents<T> = T extends { intent: infer I } ? I : never

function getFormType<
T extends Type<{ intent: string }>,
I extends ExtractIntents<inferAmbient<T>> & string,
>(type: T, intent: I) {
return (
type
// @ts-ignore
.extract({ intent: `'${intent}'` })
// @ts-ignore
.omit('intent') as unknown as Omit<
Extract<outputOf<T>, { intent: I }>,
'intent'
>
)
}

const profileType = getFormType(formType, 'profile')
type ExtractIntents<T> = T extends { intent: infer I } ? I : never

function getFormType<
T extends Type<{ intent: string }>,
I extends ExtractIntents<inferAmbient<T>> & string,
>(type: T, intent: I) {
return (
type
// @ts-ignore
.extract({ intent: `'${intent}'` })
// @ts-ignore
.omit('intent') as unknown as Omit<
Extract<outputOf<T>, { intent: I }>,
'intent'
>
)
}

const profileType = getFormType(formType, 'profile')
This works! Sorry for butchering the library 🙈
No description
ssalbdivad
ssalbdivad5mo ago
It's not your fault, it's not possible for TS to statically compute that this is safe so you have to give up type safety inside an implementation like that to have it externally Here's how I would write it though:
function getFormType<t extends { intent: string }, intent extends t["intent"]>(
type: Type<t>,
intent: intent
): Type<Omit<Extract<t, { intent: intent }>, "intent">>
function getFormType(type: Type, intent: string) {
return type
.extract({ intent: `'${intent}'` })
.as<{ intent: string }>()
.omit("intent") as never
}

const profileType = getFormType(formType, "profile")
function getFormType<t extends { intent: string }, intent extends t["intent"]>(
type: Type<t>,
intent: intent
): Type<Omit<Extract<t, { intent: intent }>, "intent">>
function getFormType(type: Type, intent: string) {
return type
.extract({ intent: `'${intent}'` })
.as<{ intent: string }>()
.omit("intent") as never
}

const profileType = getFormType(formType, "profile")
PIat
PIatOP5mo ago
So much cleaner Is it called function overloading when you declare the function name twice?
ssalbdivad
ssalbdivad5mo ago
Well like I said I'm very used to having to cast constantly haha so may as well at least minimize the chaos 😛
No description
ssalbdivad
ssalbdivad5mo ago
Yeah in TS it works a lot like casting. Any of the declarations that are not on the implementation itself act as the external signature, then internally it very loosely asserts that they're correlated but it's basically a cast
PIat
PIatOP5mo ago
I'd have two more questions 🙏 1. What is the .as function and 2. why when you cast as never it still returns the proper value
ssalbdivad
ssalbdivad5mo ago
Casting to never is just a way to allow an assignment, in this case from the return value to the statically declared type (Type<Omit<Extract<t, { intent: intent }>, "intent">>)
PIat
PIatOP5mo ago
Mindblown, neber used it before. Will need to get used to this syntax
ssalbdivad
ssalbdivad5mo ago
.as is just a way to cast a type instance Unfortunately it is not supported for arrow functions which are what I normally like to use But it works well if you're already using function
PIat
PIatOP5mo ago
Why do you use arrow functions? 👀 isn't it annoying to be able to use the function only under the declaration?
ssalbdivad
ssalbdivad5mo ago
Not really, because that only applies if you're actually writing functions you're invoking at the top-level of the module instead of from other functions which is pretty rare. It's mostly just a stylistic preference since they mirror the way functions are declared as types in TS
PIat
PIatOP5mo ago
Makes a lot of sense! Thank you for all these answers 🙏
ssalbdivad
ssalbdivad5mo ago
No problem. Hopefully will be releasing 2.0.0-beta.7 this afternoon 🙏
PIat
PIatOP5mo ago
I'm rooting for you!
Want results from more Discord servers?
Add your server