#Seeking Advice on Simplifying TypeScript Builders with Inferred Types

113 messages ยท Page 1 of 1 (latest)

sinful pagoda
#

I'm currently working on a TypeScript project where I need to define and use builders for creating objects with multiple options. I'm facing challenges with maintaining type safety while trying to simplify the process of defining builders without overly verbose generics or manually typing each method.

Here's the pattern I'm using:

small spruceBOT
#
aurimasniekis#0

Preview:```ts
interface FieldOption<
TName extends string,
TAlias extends string | undefined,
TOptional extends boolean,
TIsFancy extends boolean

{
name: TName
alias?: TAlias
isOptional?: TOptional
isFancy?: TIsFancy
}

interface FieldOptionBuilder<
A extends string,
B extends string | undefined,
C extends boolean,
D extends boolean

{
name<V extends string>(
name: V
...```

mortal dirge
#

have you considered using .optional() instead of the is- not-

sinful pagoda
#

that just example i wrote

mortal dirge
#

ok

sinful pagoda
#

I have bigger examples let me try to combine then to playground

mortal dirge
#

I think the best solution is to have the data in an object and use that as a generic type param
then, hide that type param with another generic

sinful pagoda
#

but then u would either loose types or loose value

#

The idea of the builder is to have exact values used not string or boolean

#

when u get final type u can then use it to build data structure types and etc

mortal dirge
#

should be possible

sinful pagoda
#

yeah trying to get something which wouldnt require me to copy 50 files ;D

#

lol found some of the code in here when was asking for help last year ๐Ÿ˜„

#

damn i dont know where in github the full use of this is, as i am on sofa on gaming pc on tv trying to browse my github xD

#

i will post tomorrow if I dont forget

#

@dusty elm sorry for the ping, but maybe u would have some ideas of building builders for "big" interfaces, as you work on similar infer stuff on arktype

dusty elm
#

Is there a specific question for a problem you want to solve? E.g. being able to change what properties are present on a single class based on a generic parameter

sinful pagoda
#

My goal is basically to have a builder which would fill interface like FieldOptions

#

so that later on I could use it to build other things

#

The first playground is working stuff, but then u start adding more fields it becomes really big and messy

dusty elm
#

Put them in an object instead of using individual parameters

sinful pagoda
#

u mean {} ?

#

but wouldnt that make me loose the types?

dusty elm
sinful pagoda
#

damn again type defined object

#

i always use interfaces

dusty elm
#

You can use an interface to define the "parameter" names on your object that doesn't matter

#

You will need types if you want to compute them from operations like & etc. of course

sinful pagoda
#

This is basically what i want ๐Ÿ˜„

small spruceBOT
#
aurimasniekis#0

Preview:```ts
export type evaluate<t> = {
[k in keyof t]: t[k]
} & unknown

export type merge<base, merged> = evaluate<
{
[k in Exclude<keyof base, keyof merged>]: base[k]
} & merged

type Opts = {a?: boolean; b?: boolean}
type Defaults = {a: true; b: false}
type myType<opts extends Opts> = merge<Defaults, opts>
...```

sinful pagoda
#

Could you explain the evaluate part of & unknown ? Is it to make merge types into one?

dusty elm
sinful pagoda
#

and me was using map type for that ๐Ÿ˜„

dusty elm
sinful pagoda
#

nvm i am blind

dusty elm
#

It's the combination of a mapped type and the intersection that forces it to compute

#

But a good thing to keep in mind is if you already have a mapped type, you don't always need to wrap the whole thing in evaluate

#

Sometimes you can just add & unknown to the end of your existing type

sinful pagoda
#
export type Simplify<T> = {[K in keyof T]: T[K]} & {};

That's mine in my commons lib ๐Ÿ˜„

dusty elm
#

It's actually weird that it works with & {}

#

Or not weird that it works but it's weird that it doesn't cause other problems

sinful pagoda
#

it maybe did, i had seen error of too difficult to calculate ๐Ÿ˜„

dusty elm
#

No it's fine it works the same

#
export type Simplify<T> = { [K in keyof T]: T[K] } & {}

type Z = Simplify<null>
//   ^? null
#

But this doesn't really make sense if you look at the type

#

{} & null should be never

#

Because the whole first part (the mapped type) on a primitive like that just is a no-op so evaluates to null

#

So if it were not special-cased in the compiler the output type should be never

sinful pagoda
#

Welcome to TS world where not everything makes sense ;D

dusty elm
#

Haha I've been around for a while ๐Ÿ˜›

sinful pagoda
#

worst part i am already second month on this let me fix some stuff and never touch it again

#

am more backend guy

dusty elm
#

Dealing with complex types is definitely a specialized skill

#

That most devs don't need

#

So if you're not used to it and have to do it it's usually somewhat painful

sinful pagoda
#

So many people talk how they are typescript devs, and when u give some questions or tasks they like whut ๐Ÿ˜„ I am been hiring myself some frontend devs, they all mention they work with typescript but apart foo(input: string): boolean thet dont really know much of typescript

dusty elm
#

To be fair I think the goal for most devs is that's how you use it

#

you consume libraries to provide safe types and DX and don't write many types yourself

sinful pagoda
#

Yes, but I mean if u put TS in ur cv as pro level, and you dont know how to remap interface to have prefix of foo_ then its bit sad

dusty elm
#

I don't really know if I agree but if you're looking to hire devs with a lot of type manipulation experience I think you should put that in the job description and some people will find that position much more appealing for that reason

mortal dirge
dusty elm
mortal dirge
#

ive always done this

dusty elm
#

Oh maybe it's nicer than that

sinful pagoda
#

I mean yes, but I mean when in interview, and you ask what your experience with typescript, and candidate replies oh I am working with typescript daily, and i am profesional with it. My next question you know how to make/work with advnaced types? Candidate replies oh yes for sure, I have created really difficult types and pushes typescript to the limits.

sinful pagoda
dusty elm
#

Do you have an example where this does something different than the base Simplify type you linked?

sinful pagoda
#

good question

dusty elm
#

I don't think the behavior is different

sinful pagoda
#

I will try to look up tomorrow

#

I left my laptop in office

#

as i am going tomorrow anyway

dusty elm
#

I mean I'm pretty confident it is not different

#

So I think the extra overhead is not needed

sinful pagoda
#

btw i just now read some more replies on that tweet

#

that with functions its different

mortal dirge
sinful pagoda
#

i have some thoughts about using merge for each builder method

dusty elm
dusty elm
#

compressing a union of objects? that sounds like you're changing the type

mortal dirge
#

U2I<{a: 1} | {b: 2}> gives {a: 1} & {b: 2}, I wanted to display it as {a: 1; b: 2} (those are the same type right)

sinful pagoda
#

{ foo: string } & { bar: number } to { foo: string; bar: number }

dusty elm
#

It's the same thing

#

The union to intersection thing is a transformation but that is separate from this step at the end

dusty elm
#

"skill issue"

mortal dirge
#

ok got it

#

unrelated question but, does anyone else ever use this

#

default generic type param to create a variable to trigger dct

dusty elm
#

I don't use enums

mortal dirge
#

its not an enum

dusty elm
#

But the type name implies you'd use it with enums?

mortal dirge
#

bad name

#

should be enum like class

#
class Foo {
  static a = new Foo(...);
  static b = new Foo(...);
}
#

the type returns "a" | "b"

#

emulates java enums

dusty elm
#

This is just not a pattern I use in terms of organizing data structures so I wouldn't use the type for that reason

small spruceBOT
#
export type evaluate<t> = { [k in keyof t]: t[k] } & {}

export type merge<base, merged> = evaluate<
    {
        [k in Exclude<keyof base, keyof merged>]: base[k]
    } & merged
>

type Opts = { a?: boolean; b?: boolean }
type Defaults = { a: true; b: false }
type myType<opts extends Opts> = merge<Defaults, opts>

type Config = myType<{ a: false }>
//    ^? - type Config = {
//        b: false;
//        a: false;
//    }

declare function add<T, K, V>(i: T, k: K, v: V): merge<T, { K: V }>;
declare const f: Config;

const r = add(f, 'c', 'yes');
//    ^? - const r: {
//        a: false;
//        b: false;
//        K: string;
//    }
mortal dirge
sinful pagoda
#

nvm forgot V extends string

small spruceBOT
#
export type evaluate<t> = { [k in keyof t]: t[k] } & {}

export type merge<base, merged> = evaluate<
    {
        [k in Exclude<keyof base, keyof merged>]: base[k]
    } & merged
>

type Opts = { a?: boolean; b?: boolean }
type Defaults = { a: true; b: false }
type myType<opts extends Opts> = merge<Defaults, opts>

type Config = myType<{ a: false }>
//    ^? - type Config = {
//        b: false;
//        a: false;
//    }

declare function add<T, K, V extends string>(i: T, k: K, v: V): merge<T, { K: V }>;
declare const f: Config;

const r = add(f, 'c', 'yes');
//    ^? - const r: {
//        a: false;
//        b: false;
//        K: "yes";
//    }
dusty elm
#

This syntax has to be {[_ in K]: V} as is would just do literal K

mortal dirge
#

sometimes I use it to name intermediate results too

dusty elm
#

I've stopped doing that as often and tried to just split those types up more but some people like that

mortal dirge
#

usually a generic is better but

#

sometimes it needs to be constrained to the input