#Typing Logic

59 messages · Page 1 of 1 (latest)

cinder sky
#

Hello, I want to make a dynamic type depending on the "ModerationActionType". Because some have properties some don't have. For example "Mute" has duration. This is my current solution, I know that there is a better way but I don't know how to do it.

export enum ModerationActionType {
    Ban = "BAN",
    Kick = "KICK",
    Mute = "MUTE",
    Warn = "WARN",
    Unban = "UNBAN",
    Unmute = "UNMUTE",
    Unwarn = "UNWARN"
}

export type ModerationAction = {
    type: ModerationActionType;
    createdAt: Date;
    guildId: string;
    moderatorId: string;
    targetId: string;
    reason: string;
    duration: number | null;
};

export type BaseModerationCreateOptions = Omit<ModerationAction, "createdAt" | "duration">;

export type ModerationActionBan = BaseModerationCreateOptions & {
    type: ModerationActionType.Ban;
};

export type ModerationActionKick = BaseModerationCreateOptions & {
    type: ModerationActionType.Kick;
};

export type ModerationActionMute = BaseModerationCreateOptions & {
    type: ModerationActionType.Mute;
    duration: number;
};

export type ModerationActionWarn = BaseModerationCreateOptions & {
    type: ModerationActionType.Warn;
};

export type ModerationActionUnban = BaseModerationCreateOptions & {
    type: ModerationActionType.Unban;
};

export type ModerationActionUnmute = BaseModerationCreateOptions & {
    type: ModerationActionType.Unmute;
};

export type ModerationActionUnwarn = BaseModerationCreateOptions & {
    type: ModerationActionType.Unwarn;
};

export type ModerationActionCreateOptions =
    | ModerationActionBan
    | ModerationActionKick
    | ModerationActionMute
    | ModerationActionWarn
    | ModerationActionUnban
    | ModerationActionUnmute
    | ModerationActionUnwarn;

async function createModerationAction(data: ModerationActionCreateOptions): Promise<void> {
    console.log(data);
}
#

I thought about a solution that uses generics.

#

That's something I thought about but I'm not sure how to correctly add that in case of type "Mute" duration is required and otherwise not even displayed in the intellisense. ```ts
export enum ModerationActionType {
Ban = "BAN",
Kick = "KICK",
Mute = "MUTE",
Warn = "WARN",
Unban = "UNBAN",
Unmute = "UNMUTE",
Unwarn = "UNWARN"
}

export type ModerationAction = {
type: ModerationActionType;
createdAt: Date;
guildId: string;
executorId: string;
targetId: string;
reason: string;
duration: number | null;
};

export type ModerationActionCreateOptions<T extends ModerationActionType> = Omit<ModerationAction, "createdAt" | "duration">;

wheat zinc
#

could go with an interface that notes the extra props

cyan agateBOT
#
that_guy977#0

Preview:```ts
export enum ModerationActionType {
Ban = "BAN",
Kick = "KICK",
Mute = "MUTE",
Warn = "WARN",
Unban = "UNBAN",
Unmute = "UNMUTE",
Unwarn = "UNWARN",
}

export type BaseModerationAction = {
type: ModerationActionType
cre
...```

wheat zinc
#

i put the duration on the wrong action, but i think you can see the point

#

this also works sith a union of actiontypes due to the T extends T doing distribution

cinder sky
#

Oh nvm, I can open that link 😂 Ok thank you, that looks nice

#

Hmm, it still shows the duration option for every type.

cyan agateBOT
#
walkaisa#0

Preview:```ts
export enum ModerationActionType {
Ban = "BAN",
Kick = "KICK",
Mute = "MUTE",
Warn = "WARN",
Unban = "UNBAN",
Unmute = "UNMUTE",
Unwarn = "UNWARN",
}

export type BaseModerationAction = {
type: ModerationActionType
cre
...```

cinder sky
#

Got this solution now, is there a better practice? ```ts
export enum ModerationActionType {
Ban = "BAN",
Kick = "KICK",
Mute = "MUTE",
Warn = "WARN",
Unban = "UNBAN",
Unmute = "UNMUTE",
Unwarn = "UNWARN"
}

export type ReasonRequiredModerationActionTypes = ModerationActionType.Ban | ModerationActionType.Kick | ModerationActionType.Mute | ModerationActionType.Warn;

export type ModerationActionCreateOptions<T extends ModerationActionType> = {
type: T;
guildId: string;
executorId: string;
targetId: string;
} & (T extends ModerationActionType.Mute ? { duration: number } : object) &
(T extends ReasonRequiredModerationActionTypes ? { reason: string } : { reason?: string });

wheat zinc
cinder sky
#

No, a bit different

cyan agateBOT
#
that_guy977#0

Preview:ts ... type X = ModerationActionCreateOptions<ModerationActionType.Ban> // ^? type Y = ModerationActionCreateOptions<ModerationActionType.Warn> // ^? type YEx = Expand<ModerationActionCreateOptions<ModerationActionType.Warn>> // ^? ...

wheat zinc
cinder sky
#

Doesn’t work

wheat zinc
#

it does though...

#

a bit big

wheat zinc
cyan agateBOT
#
type X = {
    type: ModerationActionType;
    guildId: string;
    executorId: string;
    targetId: string;
    reason: string;
} /* 33:6 */``````ts
type Y = Omit<BaseModerationAction, "createdAt"> & {
    duration: number;
} /* 34:6 */``````ts
type YEx = {
    type: ModerationActionType;
    guildId: string;
    executorId: string;
    targetId: string;
    reason: string;
    duration: number;
} /* 35:6 */```
wheat zinc
#

oh, i forgot to put the enum member into the intersection

cyan agateBOT
#
that_guy977#0

Preview:```ts
export enum ModerationActionType {
Ban = "BAN",
Kick = "KICK",
Mute = "MUTE",
Warn = "WARN",
Unban = "UNBAN",
Unmute = "UNMUTE",
Unwarn = "UNWARN",
}

export type BaseModerationAction = {
type: ModerationActionType
cre
...```

wheat zinc
#

!ts X Y YEx

cyan agateBOT
#
type X = Omit<BaseModerationAction, "createdAt"> & {
    type: ModerationActionType.Ban;
} /* 33:6 */``````ts
type Y = Omit<BaseModerationAction, "createdAt"> & {
    duration: number;
} & {
    type: ModerationActionType.Warn;
} /* 34:6 */``````ts
type YEx = {
    type: ModerationActionType.Warn;
    guildId: string;
    executorId: string;
    targetId: string;
    reason: string;
    duration: number;
} /* 35:6 */```
wheat zinc
#

that should be clearer

#

this pattern is used by djs for event parameters, just not as a DU

#

fwiw, it sounds like you're trying to do enums with arguments/values/members, which isn't a thing in ts, unfortunately. so it's not exactly going fluently as you can probably tell

cyan agateBOT
#
that_guy977#0

Preview:```ts
export enum ModerationActionType {
Ban = "BAN",
Kick = "KICK",
Mute = "MUTE",
Warn = "WARN",
Unban = "UNBAN",
Unmute = "UNMUTE",
Unwarn = "UNWARN",
}

export type BaseModerationAction = {
type: ModerationActionType
cre
...```

cinder sky
#

Okay thank you, I’ll try that 👌🏽

wheat zinc
#

it's basically the same thing i showed before

#

thonk you could actually just put this on BaseModerationAction

cyan agateBOT
#
that_guy977#0

Preview:```ts
export enum ModerationActionType {
Ban = "BAN",
Kick = "KICK",
Mute = "MUTE",
Warn = "WARN",
Unban = "UNBAN",
Unmute = "UNMUTE",
Unwarn = "UNWARN",
}

export type ModerationAction<
T extends ModerationActionType = ModerationActionType

= T extends T
? {
type: T
...```

wheat zinc
#

it's to enable distribution:

#

!hb distribute

cyan agateBOT
cinder sky
#

Oh okay, ty

wheat zinc
#

so if you pass in a union of types, it gets distributed out for each type instead of being a mangled unioned mess

cinder sky
#

And this is how my function would look like, correct? ```ts
public async createModerationAction<T extends ModerationActionType>(moduleId: number, data: ModerationActionCreateOptions<T>): Promise<void> {
await this.client.db.moderationAction.create({
data: {
id: this.generateCaseId(),
type: data.type,
created_at: new Date(),
guildId: data.guildId,
executorId: data.executorId,
targetId: data.targetId,
reason: data.reason,
duration: data.type === ModerationActionType.Mute ? data.duration : undefined,
module_id: moduleId
}
});
}

wheat zinc
#

just need a conditional to distribute, but don't need to actually check anything, so T extends T is a common tautology for this purpose. T extends unknown and T extends any are also used but i tend towards the first

wheat zinc
cinder sky
#

Yeah ok, understood

#

Okay

wheat zinc
#

using data.* separately might break the discriminated union behavior, i think

#

correspondance problem thing

cinder sky
#

What you declared these for? ```ts
type BanOptions = Expand<ModerationAction<ModerationActionType.Ban>>;
// ^?
type UnbanOptions = Expand<ModerationAction<ModerationActionType.Unban>>;
// ^?
type MuteOptions = Expand<ModerationAction<ModerationActionType.Mute>>;
// ^?

type Expand<T> = { [K in keyof T]: T[K] };

wheat zinc
#

those are just for demonstration

cinder sky
#

Oh okay

cinder sky
# wheat zinc those are just for demonstration

Could you maybe also help me to solve this issue?

Error

Type 'Collection<string, { type: ModerationActionType; createdAt: Date; guildId: string; executorId: string; targetId: string; reason: string | null; duration: number | null; }>' is not assignable to type 'Collection<string, ModerationAction>'.
  Type '{ type: ModerationActionType; createdAt: Date; guildId: string; executorId: string; targetId: string; reason: string | null; duration: number | null; }' is not assignable to type 'ModerationAction'.
    Type '{ type: ModerationActionType; createdAt: Date; guildId: string; executorId: string; targetId: string; reason: string | null; duration: number | null; }' is not assignable to type '{ type: ModerationActionType.Mute; createdAt: Date; guildId: string; executorId: string; targetId: string; reason?: string | undefined; } & { reason: string; duration: number; }'.
      Type '{ type: ModerationActionType; createdAt: Date; guildId: string; executorId: string; targetId: string; reason: string | null; duration: number | null; }' is not assignable to type '{ type: ModerationActionType.Mute; createdAt: Date; guildId: string; executorId: string; targetId: string; reason?: string | undefined; }'.
        Types of property 'type' are incompatible.
          Type 'ModerationActionType' is not assignable to type 'ModerationActionType.Mute'.ts(2322)
ModerationActionOptions.ts(14, 5): The expected type comes from property 'actions' which is declared here on type 'ModerationConfig'

Code

actions: new Collection(
    config.actions.map((action) => {
        return [
            action.id,
            {
                type: action.type as ModerationActionType,
                createdAt: action.created_at,
                guildId: action.guild_id,
                executorId: action.executor_id,
                targetId: action.target_id,
                reason: action.reason,
                duration: action.duration
            }
        ];
    })
)
#

The error occurs on "action" parameter.

wheat zinc
#

what's with the assertion?

#

also, seems like this is a case of the correspondence problem

#

why do you need to remap? why can't you use a consistent casing?

cinder sky
# wheat zinc why do you need to remap? why can't you use a consistent casing?

Because I prefer a different casing in databases than in typescript. Also, this is one solution I would know. ```ts
private parseModerationAction(action: RawModerationAction): ModerationAction {
const type = action.type as ModerationActionType;
switch (type) {
case ModerationActionType.Mute: {
return {
type: type,
createdAt: action.created_at,
guildId: action.guild_id,
executorId: action.executor_id,
targetId: action.target_id,
reason: action.reason!,
duration: action.duration!
};
}
default: {
return {
type: type,
createdAt: action.created_at,
guildId: action.guild_id,
executorId: action.executor_id,
targetId: action.target_id,
reason: action.reason!
};
}
}
}

wheat zinc
#

yeah that's one way to do it

cinder sky