Skip to content

Commit 9895ea8

Browse files
committed
feat: implement effect scope management for slash command interactions
1 parent 86fed21 commit 9895ea8

File tree

1 file changed

+49
-11
lines changed

1 file changed

+49
-11
lines changed

src/runtime/server/utils/client.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import type { APIApplicationCommand, APIApplicationCommandOption, ChatInputCommandInteraction, RESTGetAPIApplicationCommandsResult, RESTPatchAPIApplicationCommandResult, RESTPutAPIApplicationCommandsResult } from 'discord.js'
2+
import type { EffectScope } from 'vue'
23
import type { NuxtDiscordOptions, SlashCommandOption, SlashCommandOptionType, SlashCommandReturnType, SlashCommandRuntime } from '../../../types'
34
import process from 'node:process'
45
import { ApplicationCommandOptionType, Events, Client as InternalClient, REST, Routes, SlashCommandBuilder } from 'discord.js'
56
import { useNitroApp } from 'nitropack/runtime'
7+
import { effectScope, isRef } from 'vue'
68
import { logger } from '../../logger'
9+
import { reply } from './reply'
710

811
export interface DiscordClientErrorBase {
912
type: string
@@ -87,6 +90,8 @@ export class DiscordClient {
8790
this.#handleSlashCommand(interaction)
8891
})
8992

93+
this.startScope()
94+
9095
const { resolve, promise } = Promise.withResolvers<undefined>()
9196

9297
this.#client.once(Events.ClientReady, (readyClient) => {
@@ -105,6 +110,21 @@ export class DiscordClient {
105110
await this.#client.destroy()
106111
this.#client = null
107112
}
113+
this.stopScope()
114+
}
115+
116+
#scope?: EffectScope
117+
public startScope() {
118+
if (this.#scope)
119+
return
120+
this.#scope = effectScope(/* detached */ true)
121+
}
122+
123+
public stopScope() {
124+
if (!this.#scope)
125+
return
126+
this.#scope.stop()
127+
this.#scope = undefined
108128
}
109129

110130
public isReady(): boolean {
@@ -147,6 +167,8 @@ export class DiscordClient {
147167
return this.#slashCommands.find(cmd => cmd.name === name)
148168
}
149169

170+
#interactionScopes: WeakMap<ChatInputCommandInteraction, EffectScope> = new WeakMap()
171+
#commandFinalizationRegistry = new FinalizationRegistry((scope: EffectScope) => scope.stop())
150172
async #handleSlashCommand(interaction: ChatInputCommandInteraction): Promise<void> {
151173
const name = interaction.commandName
152174
const command = this.#slashCommands.find(cmd => cmd.name === name)
@@ -205,10 +227,26 @@ export class DiscordClient {
205227
}
206228
}
207229

230+
let scope!: EffectScope
231+
if (this.#scope) {
232+
// this scope should be a child of the client scope
233+
this.#scope.run(() => (scope = effectScope()))
234+
}
235+
else {
236+
scope = effectScope()
237+
}
238+
239+
this.#interactionScopes.set(interaction, scope)
240+
if (import.meta.dev)
241+
this.#commandFinalizationRegistry.register(command, scope)
242+
208243
try {
209-
currentInteraction = interaction
210-
const result = command.execute!(...args)
211-
currentInteraction = null
244+
let result!: SlashCommandReturnType
245+
scope.run (() => {
246+
currentInteraction = interaction
247+
result = command.execute!(...args)
248+
currentInteraction = null
249+
})
212250

213251
if (this.#clientOptions?.deferOnPromise && isPromise(result)) {
214252
await interaction.deferReply()
@@ -242,18 +280,18 @@ export class DiscordClient {
242280
}
243281

244282
try {
245-
if (typeof result === 'string') {
246-
if (interaction.deferred || interaction.replied) {
247-
return interaction.editReply({ content: result })
283+
if (typeof result === 'string' || isRef(result)) {
284+
return this.#handleSlashCommandReturn(reply(result), interaction, command)
285+
}
286+
else if (typeof result === 'function') {
287+
const scope = this.#interactionScopes.get(interaction)
288+
if (!scope) {
289+
return result.call(this, interaction, this)
248290
}
249291
else {
250-
return interaction.reply({ content: result })
292+
return scope.run(() => result.call(this, interaction, this))
251293
}
252294
}
253-
else if (typeof result === 'function') {
254-
const newResult = result.call(this, interaction, this)
255-
return this.#handleSlashCommandReturn(newResult, interaction, command)
256-
}
257295
else if (Symbol.iterator in result && typeof result[Symbol.iterator] === 'function') {
258296
const gen = result[Symbol.iterator]()
259297
let next = gen.next()

0 commit comments

Comments
 (0)