:wave:, I've been trying to get outputs working fo...
# typescript
b
👋, I've been trying to get outputs working for a Dynamic Resource Provider in TS. The outputs always return as undefined. Any thoughts? Am I missing something obvious?
c
pulumi.dynamic.ResourceProvider takes type arguments, so if you do pulumi.dynamic.ResourceProvider<Input, Output> it will work correctly.
b
Thanks for the response. The problem is not really a typing thing. The values just don't exist. TS actually has the typing right.
Copy code
import { TestOutput } from '@drippy/pulumi-utils'
import * as pulumi from '@pulumi/pulumi'

const test = new TestOutput('my-test', { ref: 'hello' })
export const ref = test.ref
if (typeof test.ref === 'undefined') {
  await pulumi.log.info('I am undefined')
}
Copy code
Diagnostics:
  pulumi:pulumi:Stack (temp-bobby):
    I am undefined
    warning: Undefined value (ref) will not show as a stack output.
c
Hmm, I'm not sure then. This is from my old project, where I tested out a dynamic provider:
Copy code
import * as pulumi from "@pulumi/pulumi"

type DiscordSlashCommandArgs = {
  application_id: string,
  bot_token: string,
  guild_id?: string,
  command: CommandArgs
}

export type CommandArgs = Omit<ApplicationCommand, "id" | "application_id" | "guild_id" | "version">

type DiscordSlashCommandOutputs =
  Omit<DiscordSlashCommandArgs, "command">
  & { command: ApplicationCommand }

// Object enum pattern
// Using external (Discord's) naming for enum
export const CommandType = {
  CHAT_INPUT: 1,
  USER: 2,
  MESSAGE: 3,
} as const
type CommandType = typeof CommandType[keyof typeof CommandType];

type ApplicationCommand = {
  id: string
  type?: CommandType
  application_id: string
  guild_id?: string
  name: string
  name_localizations?: Record<string, string>[]
  description: string
  description_localizations?: Record<string, string>[]
  options?: unknown
  default_member_permissions?: string
  dm_permission?: boolean
  default_permission?: boolean
  nsfw?: boolean
  version: string
}

const discordRequest = async (
  applicationId: string, guildId: string | undefined, commandId: string | undefined, botToken: string,
  httpMethod: "GET" | "PATCH" | "DELETE" | "POST", body?: CommandArgs | ApplicationCommand) => {
  const optionalGuildId = `${guildId ? `/guilds/${guildId}` : ""}`
  const optionalCommandId = `${commandId ? `/${commandId}` : ""}`
  const url = `<https://discord.com/api/v10/applications/${applicationId}${optionalGuildId}/commands${optionalCommandId}>`

  const headers = {
    "Authorization": `Bot ${botToken}`,
    "Content-Type": "application/json",
  }

  const response = await fetch(url,
    {
      method: httpMethod,
      headers: headers,
      body: body && JSON.stringify(body),
    })

  const command: ApplicationCommand | undefined = response.body && await response.json()

  if (!response.ok) {
    throw new Error(JSON.stringify(command))
  }

  return command
}

const updateOutput = (
  news: DiscordSlashCommandArgs,
  command?: ApplicationCommand,
  olds?: DiscordSlashCommandOutputs,
): DiscordSlashCommandOutputs | undefined => {
  if (!command) {
    return undefined
  }

  return {
    ...news,
    command: { ...(olds ? olds.command : {}), ...command },
  }
}

const DiscordSlashCommandProvider: pulumi.dynamic.ResourceProvider<DiscordSlashCommandArgs, DiscordSlashCommandOutputs> = {
  async create (inputs) {
    const command = await discordRequest(inputs.application_id, inputs.guild_id, undefined, inputs.bot_token, "POST", inputs.command)
    if (!command) {
      throw new Error(`Error creating dynamic/discord:SlashCommand, no body returned`)
    }
    return { id: command.id, outs: updateOutput(inputs, command) }
  },
  async read (id, props) {
    if (!props) {
      throw new Error(`Error reading dynamic/discord:SlashCommand ${id}, no props was undefined`)
    }
    const command = await discordRequest(props.application_id, props.guild_id, props.command.id, props.bot_token, "GET")

    return { id: id, props: updateOutput(props, command) }
  },
  async update (id, olds, news) {
    const command = await discordRequest(news.application_id, news.guild_id, olds.command.id, news.bot_token, "PATCH", news.command)

    return { outs: updateOutput(news, command, olds) }
  },
  async delete (id, props) {
    await discordRequest(props.application_id, props.guild_id, props.command.id, props.bot_token, "DELETE")
  },
}


type PulumiInputWrapper<T> = {
  [P in keyof T]: pulumi.Input<T[P]>;
};

export type DiscordSlashCommandInputs = PulumiInputWrapper<DiscordSlashCommandArgs>

export class DiscordSlashCommand extends pulumi.dynamic.Resource {
  public readonly command!: pulumi.Output<ApplicationCommand>

  constructor (
    name: string,
    args: DiscordSlashCommandInputs,
    opts?: pulumi.CustomResourceOptions,
  ) {
    super(DiscordSlashCommandProvider, name, args, opts, "discord", "SlashCommand")
  }
}

const discordSlashCommandPeek = new DiscordSlashCommand("peek", {
  application_id: discordApplicationId,
  bot_token: discordBotToken,
  guild_id: discordGuildId,
  command: {
    name: "peek",
    type: CommandType.CHAT_INPUT,
    description: "replies with stuff",
  },
})