Skip to content

Commit

Permalink
refactor: better option type safety
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Smart committed Dec 7, 2022
1 parent 9f27f74 commit 68f1e63
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 23 deletions.
8 changes: 7 additions & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@
"dependencies": {
"@effect/io": "^0.0.30",
"@fp-ts/data": "^0.0.20",
"dfx": "^0.14.0",
"dfx": "^0.15.0",
"dotenv": "^16.0.3",
"fastify": "^4.10.2"
},
"devDependencies": {
"esbuild": "^0.15.18",
"ts-node": "^10.9.1",
"typescript": "^4.9.3"
},
"pnpm": {
"overrides": {
"@effect/io": "^0.0.30",
"@fp-ts/data": "^0.0.20"
}
}
}
12 changes: 8 additions & 4 deletions examples/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 40 additions & 2 deletions examples/src/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Cause from "@effect/io/Cause"
import * as Effect from "@effect/io/Effect"
import * as Exit from "@effect/io/Exit"
import { pipe } from "@fp-ts/data/Function"
import { Ix } from "dfx"
import { Discord, Ix } from "dfx"
import { make, runIx } from "dfx/gateway"
import Dotenv from "dotenv"

Expand All @@ -28,8 +28,46 @@ const hello = Ix.global(
}),
)

// Optionally use the type safe helpers
const greeting = Ix.global(
{
name: "greeting",
description: "A basic command",
options: [
{
type: Discord.ApplicationCommandOptionType.STRING,
name: "who",
description: "who to greet",
required: true,
},
{
type: Discord.ApplicationCommandOptionType.STRING,
name: "greeting",
description: "What kind of greeting?",
},
],
},
(i) =>
pipe(
Effect.struct({
who: i.optionValue("who"),
greeting: pipe(
i.optionValueOptional("greeting"),
Effect.someOrElse(() => "Hello"),
),
// fail: i.optionValue("fail"), // <- this would be a type error
}),
Effect.map(({ who, greeting }) => ({
type: 4,
data: {
content: `${greeting} ${who}!`,
},
})),
),
)

// Build your program use `Ix.builder`
const ix = Ix.builder.add(hello)
const ix = Ix.builder.add(hello).add(greeting)

// Run it
pipe(
Expand Down
44 changes: 30 additions & 14 deletions src/Interactions/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export interface CommandHelper<A> {
>

optionValue: (
name: CommandOptions<A>["name"],
name: RequiredCommandOptions<A>["name"],
) => Effect<Discord.ApplicationCommandDatum, RequiredOptionNotFound, string>

optionValueOptional: (
Expand All @@ -166,18 +166,20 @@ export interface CommandHelper<A> {
>

subCommandOptionValue: (
name: SubCommandOptions<A>["name"],
name: RequiredSubCommandOptions<A>["name"],
) => Effect<SubCommandContext, RequiredOptionNotFound, string>

subCommandOptionValueOptional: (
name: SubCommandOptions<A>["name"],
) => Effect<SubCommandContext, never, Maybe<string>>

subCommands: <
NER extends Record<
SubCommands<A>["name"],
Effect<any, any, Discord.InteractionResponse>
>,
NER extends SubCommands<A> extends never
? never
: Record<
SubCommands<A>["name"],
Effect<any, any, Discord.InteractionResponse>
>,
>(
commands: NER,
) => Effect<
Expand Down Expand Up @@ -207,23 +209,23 @@ type CommandHandlerFn<R, E, A> = (

// Extract option names
type ExtractOptions<A, T> = A extends {
name: string
type: T
name: string
options?: Discord.ApplicationCommandOption[]
}
?
| A
| (A extends {
options: Discord.ApplicationCommandOption[]
}
? ExtractOptions<A["options"][number], T>
: never)
? A
: A extends {
options: Discord.ApplicationCommandOption[]
}
? ExtractOptions<A["options"][number], T>
: never

type RequiredOptions<A, T> = A extends {
options: Discord.ApplicationCommandOption[]
}
? Extract<A["options"][number], { type: T; required: true }>
: never

type CommandOptions<A> = ExtractOptions<
A,
Exclude<
Expand All @@ -233,6 +235,15 @@ type CommandOptions<A> = ExtractOptions<
>
>

type RequiredCommandOptions<A> = RequiredOptions<
A,
Exclude<
Discord.ApplicationCommandOptionType,
| Discord.ApplicationCommandOptionType.SUB_COMMAND
| Discord.ApplicationCommandOptionType.SUB_COMMAND_GROUP
>
>

type SubCommands<A> = ExtractOptions<
A,
Discord.ApplicationCommandOptionType.SUB_COMMAND
Expand All @@ -243,6 +254,11 @@ type SubCommandOptions<A> = Exclude<
undefined
>[number]

type RequiredSubCommandOptions<A> = Extract<
Exclude<SubCommands<A>["options"], undefined>[number],
{ required: true }
>

type Resolvables<A> = ExtractOptions<
A,
| Discord.ApplicationCommandOptionType.ROLE
Expand Down
3 changes: 1 addition & 2 deletions src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ import type { Equal } from "@fp-ts/data/Equal"
/**
* @tsplus global
*/
import type { Option as Maybe } from "@fp-ts/data/Option"
export type { Option as Maybe } from "@fp-ts/data/Option"
import type { Maybe } from "dfx"

/**
* @tsplus global
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ export * as IxHelpers from "./Helpers/interactions.js"
export * as Members from "./Helpers/members.js"
export * as Perms from "./Helpers/permissions.js"
export * as UI from "./Helpers/ui.js"

// Hack for tsplus globals
export type { Option as Maybe } from "@fp-ts/data/Option"

0 comments on commit 68f1e63

Please sign in to comment.