A
arktype3mo ago
errata

extracting redux-like actions from type.enumerated discriminated union

Trying unsuccessfully to convert some redux/reducer-like "action" object types to arktype:
type TestAction1 = { type: 'ACTION_1', value: number }
type TestAction2 = { type: 'ACTION_2', name: string}
type TestAction = TestAction1 | TestAction2

function CreateTestAction1(): Extract<TestAction, {type: 'ACTION_1'}> {
return { type: 'ACTION_1', value: 3 }
}

const testAction1 = CreateTestAction1() // type === TestAction1
type TestAction1 = { type: 'ACTION_1', value: number }
type TestAction2 = { type: 'ACTION_2', name: string}
type TestAction = TestAction1 | TestAction2

function CreateTestAction1(): Extract<TestAction, {type: 'ACTION_1'}> {
return { type: 'ACTION_1', value: 3 }
}

const testAction1 = CreateTestAction1() // type === TestAction1
Below is how I did the conversion. The Extract on the type.enumerated results in a never and I don't follow why.
const ArkAction1 = type({ type: type.unit("ACTION_1"), value: type.number })
const ArkAction2 = type({ type: type.unit("ACTION_2"), name: type.string })
const ArkAction = type.enumerated(ArkAction1, ArkAction2)
type ArkAction = type.infer<typeof ArkAction>

function CreateArkAction1(): Extract<ArkAction, { type: 'ACTION_1'}> {
return { type: 'ACTION_1', value: 3 } // type not assignable to never
}
const ArkAction1 = type({ type: type.unit("ACTION_1"), value: type.number })
const ArkAction2 = type({ type: type.unit("ACTION_2"), name: type.string })
const ArkAction = type.enumerated(ArkAction1, ArkAction2)
type ArkAction = type.infer<typeof ArkAction>

function CreateArkAction1(): Extract<ArkAction, { type: 'ACTION_1'}> {
return { type: 'ACTION_1', value: 3 } // type not assignable to never
}
If I define the action union type in vanilla TS (from the ark inferred types) the extract works as expected:
type ArkAction1 = type.infer<typeof ArkAction1>
type ArkAction2 = type.infer<typeof ArkAction2>
type ArkAction = ArkAction1 | ArkAction2
type ArkAction1 = type.infer<typeof ArkAction1>
type ArkAction2 = type.infer<typeof ArkAction2>
type ArkAction = ArkAction1 | ArkAction2
Am I holding it wrong?
45 Replies
Dimava
Dimava3mo ago
Enumerated is array.includes, not a union You need A1.or(A2) Lemme actual rewrite your code
errata
errataOP3mo ago
are you sure? because when I look at the resulting type of the infer on the type.enumerated type ArkAction = type.infer<typeof ArkAction> it looks like this:
type ArkAction = Type<{ type: 'ACTION_1'; value: number }, {}> | Type<{ name: string; type: 'ACTION_2' }, {}>
type ArkAction = Type<{ type: 'ACTION_1'; value: number }, {}> | Type<{ name: string; type: 'ACTION_2' }, {}>
https://github.com/arktypeio/arktype/blob/da2f08d559e7c9a8e95ca513227a1496f9be3041/ark/type/__tests__/type.test.ts#L126-L130
errata
errataOP3mo ago
No description
Dimava
Dimava3mo ago
import { type } from 'arktype'

const ArkAction1 = type({ type: "'ACTION_1'", value: 'number' })
const ArkAction2 = type({ type: "'ACTION_2'", name: 'string ' })
const ArkAction = ArkAction1.or(ArkAction2)
type ArkAction = typeof ArkAction.infer

function CreateArkAction1(): Extract<ArkAction, { type: 'ACTION_1' }> {
return { type: 'ACTION_1', value: 3 }
}
import { type } from 'arktype'

const ArkAction1 = type({ type: "'ACTION_1'", value: 'number' })
const ArkAction2 = type({ type: "'ACTION_2'", name: 'string ' })
const ArkAction = ArkAction1.or(ArkAction2)
type ArkAction = typeof ArkAction.infer

function CreateArkAction1(): Extract<ArkAction, { type: 'ACTION_1' }> {
return { type: 'ACTION_1', value: 3 }
}
errata
errataOP3mo ago
hm, it does work
Dimava
Dimava3mo ago
It's for const typeValue = type(['===', 'ACTION_1', 'ACTION_2']) i.e. for enums
errata
errataOP3mo ago
yeah i basically have an enum though, right?
Dimava
Dimava3mo ago
No A enum of let a = {}, b = [] is [a, b].includes(v), not [{}, []].includes(v) as different object instances are not equal
errata
errataOP3mo ago
ahh ahhh right === ok got it
Dimava
Dimava3mo ago
So you are checking if your object is one of two ArkType instances lol
errata
errataOP3mo ago
ahhh ok ok, thank you that makes sense
Dimava
Dimava3mo ago
BTW how do you think it should be named? The make union one and the make array of values one? I do not feel like it's named perfectly so what would you guess they are named if you never used ArkType? May be more then one variant May be inspired by other validators
errata
errataOP3mo ago
enumerated is ok, that's actually how I assumed it worked previously and .or is fine as well totally though I'm not keen on the chaining
Dimava
Dimava3mo ago
@ssalbdivad we need type.oneOf or type.discriminatedUnion equivalent as I expect peoples to want a way to avoid .or chaining
errata
errataOP3mo ago
type.union?
Dimava
Dimava3mo ago
Yes And I want an AI type generator on the AT page (╯°□°)╯︵ ┻━┻ "Paste your code here to get the right (darn I forgot the word again) ArkType defs"
Dimava
Dimava3mo ago
Well, it works fine Why don't you just use its output btw
errata
errataOP3mo ago
I don't like the strings
Dimava
Dimava3mo ago
string definitions???
errata
errataOP3mo ago
refactoring
Dimava
Dimava3mo ago
explain plz
errata
errataOP3mo ago
too much magic for me
Dimava
Dimava3mo ago
ah, renaming constants?
errata
errataOP3mo ago
for example
Dimava
Dimava3mo ago
Well, but they are pretty powerful
errata
errataOP3mo ago
yeah, if i rename something via the editor, I don't want to have to think about whether I have a string somewhere with the wrong name
Dimava
Dimava3mo ago
But that's the point of ArkType 😭 lol Hmmmmm You can't rename literals And renaming non-optional values does work @errata try to learn the string way anyways, as it has looooots of features e.g. you may want '0 < integer' type
errata
errataOP3mo ago
right but 'TestAction1 | TestAction2'
Dimava
Dimava3mo ago
Well, yep, this thing in scopes is not pretty But it allows recursion so eh
errata
errataOP3mo ago
yeah I'm using that kind of syntax for validations on primitives no lost functionality when used for that kind of thing i.e. I'm using ts in the first place because I want things like refactorings to be no-brainer operations though, the compiler will error and I won't be able to build a broken app, even if I do have some strings to rename which is good (so long as I don't do something like swap the names of compatible types)
ssalbdivad
ssalbdivad3mo ago
As you say, there are already a lot of definition types, each of which I think is ideal for some situation. Adding helper methods for union/intersection etc. as an alternative to chaining feels like adding another for extremely marginal value.
Dimava
Dimava3mo ago
You do need a union helper for when you have 10s of discriminated items e.g. Event tl;rd add it next time someone asks how to union 10+ items
ssalbdivad
ssalbdivad3mo ago
A huge benefit of the type system is not having to manually discriminate
Dimava
Dimava3mo ago
I mean this
interface GlobalEventHandlersEventMap {
"abort": UIEvent;
"animationcancel": AnimationEvent;
"animationend": AnimationEvent;
"animationiteration": AnimationEvent;
"animationstart": AnimationEvent;
"auxclick": MouseEvent;
"beforeinput": InputEvent;
"beforetoggle": Event;
"blur": FocusEvent;
"cancel": Event;
"canplay": Event;
"canplaythrough": Event;
"change": Event;
"click": MouseEvent;
"close": Event;
"compositionend": CompositionEvent;
"compositionstart": CompositionEvent;
"compositionupdate": CompositionEvent;
"contextmenu": MouseEvent;
"copy": ClipboardEvent;
"cuechange": Event;
"cut": ClipboardEvent;
"dblclick": MouseEvent;
"drag": DragEvent;
"dragend": DragEvent;
"dragenter": DragEvent;
"dragleave": DragEvent;
"dragover": DragEvent;
"dragstart": DragEvent;
"drop": DragEvent;
"durationchange": Event;
"emptied": Event;
"ended": Event;
"error": ErrorEvent;
"focus": FocusEvent;
"focusin": FocusEvent;
"focusout": FocusEvent;
"formdata": FormDataEvent;
"gotpointercapture": PointerEvent;
"input": Event;
"invalid": Event;
"keydown": KeyboardEvent;
"keypress": KeyboardEvent;
"keyup": KeyboardEvent;
"load": Event;
"loadeddata": Event;
"loadedmetadata": Event;
"loadstart": Event;
"lostpointercapture": PointerEvent;
"mousedown": MouseEvent;
"mouseenter": MouseEvent;
"mouseleave": MouseEvent;
"mousemove": MouseEvent;
"mouseout": MouseEvent;
"mouseover": MouseEvent;
"mouseup": MouseEvent;
"paste": ClipboardEvent;
"pause": Event;
"play": Event;
"playing": Event;
"pointercancel": PointerEvent;
"pointerdown": PointerEvent;
"pointerenter": PointerEvent;
/// i'm out of characters
}
interface GlobalEventHandlersEventMap {
"abort": UIEvent;
"animationcancel": AnimationEvent;
"animationend": AnimationEvent;
"animationiteration": AnimationEvent;
"animationstart": AnimationEvent;
"auxclick": MouseEvent;
"beforeinput": InputEvent;
"beforetoggle": Event;
"blur": FocusEvent;
"cancel": Event;
"canplay": Event;
"canplaythrough": Event;
"change": Event;
"click": MouseEvent;
"close": Event;
"compositionend": CompositionEvent;
"compositionstart": CompositionEvent;
"compositionupdate": CompositionEvent;
"contextmenu": MouseEvent;
"copy": ClipboardEvent;
"cuechange": Event;
"cut": ClipboardEvent;
"dblclick": MouseEvent;
"drag": DragEvent;
"dragend": DragEvent;
"dragenter": DragEvent;
"dragleave": DragEvent;
"dragover": DragEvent;
"dragstart": DragEvent;
"drop": DragEvent;
"durationchange": Event;
"emptied": Event;
"ended": Event;
"error": ErrorEvent;
"focus": FocusEvent;
"focusin": FocusEvent;
"focusout": FocusEvent;
"formdata": FormDataEvent;
"gotpointercapture": PointerEvent;
"input": Event;
"invalid": Event;
"keydown": KeyboardEvent;
"keypress": KeyboardEvent;
"keyup": KeyboardEvent;
"load": Event;
"loadeddata": Event;
"loadedmetadata": Event;
"loadstart": Event;
"lostpointercapture": PointerEvent;
"mousedown": MouseEvent;
"mouseenter": MouseEvent;
"mouseleave": MouseEvent;
"mousemove": MouseEvent;
"mouseout": MouseEvent;
"mouseover": MouseEvent;
"mouseup": MouseEvent;
"paste": ClipboardEvent;
"pause": Event;
"play": Event;
"playing": Event;
"pointercancel": PointerEvent;
"pointerdown": PointerEvent;
"pointerenter": PointerEvent;
/// i'm out of characters
}
ssalbdivad
ssalbdivad3mo ago
The entire benefit boils down to writing , instead of .or(
PIat
PIat3mo ago
I'd guess enum, since TS uses enum
Dimava
Dimava3mo ago
+1 enum or literal
ssalbdivad
ssalbdivad3mo ago
Also you could just define that as an object
Dimava
Dimava3mo ago
Well, yes, what do I do to union values?
ssalbdivad
ssalbdivad3mo ago
o.get(o.keyof())
Dimava
Dimava3mo ago
Okay that's fine But you need to doc that
ssalbdivad
ssalbdivad3mo ago
Right like enum is not an overloaded term that causes confusion in TS lol Above all else, I want to make sure my names don't create bad intuitions. Stuff like .unit instead of .literal I get is less intuitive for people coming from Zod, but it's a much more unambiguous description of what kind of type you are creating once you learn the term.
PIat
PIat3mo ago
Ah, right 👍👍👍
ssalbdivad
ssalbdivad3mo ago
As discussed in the past, {} and [] are also literals and I'd likely expect they would be checked for deep equality based on that, not using ===
Want results from more Discord servers?
Add your server