diff --git a/packages/reacord/library.new.new/core/button.tsx b/packages/reacord/library.new.new/core/button.tsx
deleted file mode 100644
index 5d236e3..0000000
--- a/packages/reacord/library.new.new/core/button.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { APIMessageComponentButtonInteraction } from "discord.js"
-import { randomUUID } from "node:crypto"
-import type { ReactNode } from "react"
-import React from "react"
-import type { ComponentEvent } from "./component-event.js"
-import { Node } from "./node.js"
-import { ReacordElement } from "./reacord-element.js"
-
-export type ButtonProps = {
- /** The text on the button. Rich formatting (markdown) is not supported here. */
- label?: ReactNode
-
- /** The text on the button. Rich formatting (markdown) is not supported here.
- * If both `label` and `children` are passed, `children` will be ignored.
- */
- children?: ReactNode
-
- /** When true, the button will be slightly faded, and cannot be clicked. */
- disabled?: boolean
-
- /**
- * Renders an emoji to the left of the text.
- * Has to be a literal emoji character (e.g. 🍍),
- * or an emoji code, like `<:plus_one:778531744860602388>`.
- *
- * To get an emoji code, type your emoji in Discord chat
- * with a backslash `\` in front.
- * The bot has to be in the emoji's guild to use it.
- */
- emoji?: string
-
- /**
- * The style determines the color of the button and signals intent.
- * @see https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
- */
- style?: "primary" | "secondary" | "success" | "danger"
-
- /**
- * Happens when a user clicks the button.
- */
- onClick: (event: ButtonClickEvent) => void
-}
-
-/**
- * @category Button
- */
-export type ButtonClickEvent = ComponentEvent & {
- interaction: APIMessageComponentButtonInteraction
-}
-
-export function Button({ label, children, ...props }: ButtonProps) {
- return (
- new ButtonNode(props)}>
- {label ?? children}
-
- )
-}
-
-export class ButtonNode extends Node {
- readonly customId = randomUUID()
-}
diff --git a/packages/reacord/library.new.new/core/embed.tsx b/packages/reacord/library.new.new/core/embed.tsx
deleted file mode 100644
index c725f26..0000000
--- a/packages/reacord/library.new.new/core/embed.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import type { ReactNode } from "react"
-import React from "react"
-import type { Except } from "type-fest"
-
-export type EmbedProps = {
- title?: string
- description?: string
- url?: string
- color?: number
- fields?: Array<{ name: string; value: string; inline?: boolean }>
- author?: { name: string; url?: string; iconUrl?: string }
- thumbnail?: { url: string }
- image?: { url: string }
- video?: { url: string }
- footer?: { text: string; iconUrl?: string }
- timestamp?: string | number | Date
- children?: ReactNode
-}
-
-export function Embed({ children, ...props }: EmbedProps) {
- return {children}
-}
-
-declare global {
- // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
- interface ReacordHostElementMap {
- "reacord-embed": Except
- }
-}
diff --git a/packages/reacord/library.new.new/core/make-message-update-payload.ts b/packages/reacord/library.new.new/core/make-message-update-payload.ts
deleted file mode 100644
index 2bc99c4..0000000
--- a/packages/reacord/library.new.new/core/make-message-update-payload.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { omit } from "@reacord/helpers/omit"
-import type {
- APIActionRowComponent,
- APIEmbed,
- APIMessageActionRowComponent,
-} from "discord.js"
-import type { HostElement } from "./host-element.js"
-
-export type MessageUpdatePayload = {
- content: string
- embeds: APIEmbed[]
- components: Array>
-}
-
-export function makeMessageUpdatePayload(
- tree: HostElement,
-): MessageUpdatePayload {
- return {
- content: tree.children
- .map((child) => (child.type === "reacord-text" ? child.props.text : ""))
- .join(""),
-
- embeds: tree.children.flatMap((child) => {
- if (child.type !== "reacord-embed") return []
-
- const embed: APIEmbed = omit(child.props, ["timestamp"])
-
- if (child.props.timestamp != undefined) {
- embed.timestamp = new Date(child.props.timestamp).toISOString()
- }
-
- return embed
- }),
-
- components: [],
- }
-}
diff --git a/packages/reacord/library.new.new/core/node.ts b/packages/reacord/library.new.new/core/node.ts
deleted file mode 100644
index 4a49d22..0000000
--- a/packages/reacord/library.new.new/core/node.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-export class Node {
- private readonly _children: Node[] = []
-
- constructor(public props: Props) {}
-
- get children(): readonly Node[] {
- return this._children
- }
-
- clear() {
- this._children.splice(0)
- }
-
- add(...nodes: Node[]) {
- this._children.push(...nodes)
- }
-
- remove(node: Node) {
- const index = this._children.indexOf(node)
- if (index !== -1) this._children.splice(index, 1)
- }
-
- insertBefore(node: Node, beforeNode: Node) {
- const index = this._children.indexOf(beforeNode)
- if (index !== -1) this._children.splice(index, 0, node)
- }
-
- replace(oldNode: Node, newNode: Node) {
- const index = this._children.indexOf(oldNode)
- if (index !== -1) this._children[index] = newNode
- }
-
- clone(): this {
- const cloned: this = new (this.constructor as any)()
- cloned.add(...this.children.map((child) => child.clone()))
- return cloned
- }
-
- *walk(): Generator {
- yield this
- for (const child of this.children) {
- yield* child.walk()
- }
- }
-}
diff --git a/packages/reacord/library.new.new/core/reacord-element.ts b/packages/reacord/library.new.new/core/reacord-element.ts
deleted file mode 100644
index bfcd960..0000000
--- a/packages/reacord/library.new.new/core/reacord-element.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { createElement } from "react"
-import type { Node } from "./node.js"
-
-export type ReacordElementHostProps = {
- factory: ReacordElementFactory
-}
-
-export function ReacordElement(props: {
- createNode: () => Node
- children?: React.ReactNode
-}) {
- return createElement(
- "reacord-element",
- { factory: new ReacordElementFactory(props.createNode) },
- props.children,
- )
-}
-
-export class ReacordElementFactory {
- constructor(public readonly createNode: () => Node) {}
-
- static unwrap(maybeFactory: unknown): Node {
- if (maybeFactory instanceof ReacordElementFactory) {
- return maybeFactory.createNode()
- }
- const received = (maybeFactory as any)?.constructor.name
- throw new TypeError(
- `Expected a ${ReacordElementFactory.name}, got ${received}`,
- )
- }
-}
diff --git a/packages/reacord/library.new.new/core/reacord-instance.ts b/packages/reacord/library.new.new/core/reacord-instance.ts
deleted file mode 100644
index 9343a75..0000000
--- a/packages/reacord/library.new.new/core/reacord-instance.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import type { ReactNode } from "react"
-import type { ButtonClickEvent } from "./button.js"
-import { ButtonNode } from "./button.js"
-import type { HostElement } from "./host-element.js"
-import { Node } from "./node.js"
-import { reconciler } from "./reconciler.js"
-
-export type ReacordRenderer = {
- updateMessage(tree: HostElement): Promise
-}
-
-export class ReacordInstance {
- readonly currentTree = new Node()
- private latestTree?: Node
- private readonly reconcilerContainer = reconciler.createContainer()
-
- constructor(private readonly renderer: ReacordRenderer) {}
-
- render(content?: ReactNode) {
- reconciler.updateContainer(content, this.reconcilerContainer)
- }
-
- async updateMessage(tree: Node) {
- await this.renderer.updateMessage(tree)
- this.latestTree = tree
- }
-
- handleButtonInteraction(customId: string, event: ButtonClickEvent) {
- if (!this.latestTree) return
- for (const node of this.latestTree.walk()) {
- if (node instanceof ButtonNode && node.customId === customId) {
- node.props.onClick(event)
- }
- }
- }
-}
diff --git a/packages/reacord/library.new.new/core/reconciler.ts b/packages/reacord/library.new.new/core/reconciler.ts
deleted file mode 100644
index a4531e8..0000000
--- a/packages/reacord/library.new.new/core/reconciler.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-/* eslint-disable unicorn/prefer-modern-dom-apis */
-import ReactReconciler from "react-reconciler"
-import { DefaultEventPriority } from "react-reconciler/constants"
-import type { Node } from "./node.js"
-import type { ReacordElementHostProps } from "./reacord-element.js"
-import { ReacordElementFactory } from "./reacord-element.js"
-import type { ReacordInstance } from "./reacord-instance.js"
-import { TextNode } from "./text-node.js"
-
-// technically elements of any shape can go through the reconciler,
-// so I'm typing this as unknown to ensure we validate the props
-// before using them
-type ReconcilerProps = {
- [_ in keyof ReacordElementHostProps]?: unknown
-}
-
-export const reconciler = ReactReconciler<
- string, // Type
- ReconcilerProps, // Props
- ReacordInstance, // Container
- Node, // Instance
- TextNode, // TextInstance
- never, // SuspenseInstance
- never, // HydratableInstance
- never, // PublicInstance
- {}, // HostContext
- true, // UpdatePayload
- never, // ChildSet
- NodeJS.Timeout, // TimeoutHandle
- -1 // NoTimeout
->({
- isPrimaryRenderer: true,
- supportsMutation: true,
- supportsHydration: false,
- supportsPersistence: false,
- scheduleTimeout: setTimeout,
- cancelTimeout: clearTimeout,
- noTimeout: -1,
-
- createInstance(type, props) {
- return ReacordElementFactory.unwrap(props.factory)
- },
-
- createTextInstance(text) {
- return new TextNode(text)
- },
-
- appendInitialChild(parent, child) {
- parent.add(child)
- },
-
- appendChild(parent, child) {
- parent.add(child)
- },
-
- appendChildToContainer(container, child) {
- container.currentTree.add(child)
- },
-
- insertBefore(parent, child, beforeChild) {
- parent.insertBefore(child, beforeChild)
- },
-
- insertInContainerBefore(container, child, beforeChild) {
- container.currentTree.insertBefore(child, beforeChild)
- },
-
- removeChild(parent, child) {
- parent.remove(child)
- },
-
- removeChildFromContainer(container, child) {
- container.currentTree.remove(child)
- },
-
- clearContainer(container) {
- container.currentTree.clear()
- },
-
- commitTextUpdate(node, oldText, newText) {
- node.text = newText
- },
-
- prepareUpdate() {
- return true
- },
-
- commitUpdate(node, updatePayload, type, prevProps, nextProps) {
- node.props = ReacordElementFactory.unwrap(nextProps.factory).props
- },
-
- prepareForCommit() {
- // eslint-disable-next-line unicorn/no-null
- return null
- },
-
- resetAfterCommit(container) {
- container.updateMessage(container.currentTree.clone()).catch(console.error)
- },
-
- finalizeInitialChildren() {
- return false
- },
-
- shouldSetTextContent() {
- return false
- },
-
- getRootHostContext() {
- return {}
- },
-
- getChildHostContext() {
- return {}
- },
-
- getPublicInstance() {
- throw new Error("Refs are not supported")
- },
-
- preparePortalMount() {},
-
- getCurrentEventPriority() {
- return DefaultEventPriority
- },
-
- getInstanceFromNode() {
- return undefined
- },
-
- beforeActiveInstanceBlur() {},
- afterActiveInstanceBlur() {},
- prepareScopeUpdate() {},
- getInstanceFromScope() {
- // eslint-disable-next-line unicorn/no-null
- return null
- },
- detachDeletedInstance() {},
-})
diff --git a/packages/reacord/library.new.new/core/text-node.ts b/packages/reacord/library.new.new/core/text-node.ts
deleted file mode 100644
index 73002dd..0000000
--- a/packages/reacord/library.new.new/core/text-node.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Node } from "./node.js"
-
-export class TextNode extends Node {
- constructor(public text: string) {
- super()
- }
-}
diff --git a/packages/reacord/library.new.new/djs/reacord-discord-js.ts b/packages/reacord/library.new.new/djs/reacord-discord-js.ts
deleted file mode 100644
index af8ebe8..0000000
--- a/packages/reacord/library.new.new/djs/reacord-discord-js.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import type {
- APIMessageComponentButtonInteraction,
- Client,
- Message,
- TextChannel,
-} from "discord.js"
-import type { ReactNode } from "react"
-import type { HostElement } from "../core/host-element.js"
-import { makeMessageUpdatePayload } from "../core/make-message-update-payload.js"
-import type { ReacordRenderer } from "../core/reacord-instance.js"
-import { ReacordInstance } from "../core/reacord-instance.js"
-
-export class ReacordDiscordJs {
- instances: ReacordInstance[] = []
-
- constructor(private readonly client: Client) {
- client.on("interactionCreate", (interaction) => {
- if (!interaction.inGuild()) return
-
- if (interaction.isButton()) {
- const json =
- interaction.toJSON() as APIMessageComponentButtonInteraction
-
- for (const instance of this.instances) {
- instance.handleButtonInteraction(interaction.customId, {
- interaction: json,
- reply: () => {},
- ephemeralReply: () => {},
- })
- }
- }
-
- if (interaction.isSelectMenu()) {
- // etc.
- }
- })
- }
-
- send(channelId: string, initialContent?: ReactNode) {
- const instance = new ReacordInstance(
- new ChannelMessageRenderer(this.client, channelId),
- )
- if (initialContent !== undefined) {
- instance.render(initialContent)
- }
- }
-}
-
-class ChannelMessageRenderer implements ReacordRenderer {
- private message?: Message
-
- constructor(
- private readonly client: Client,
- private readonly channelId: string,
- ) {}
-
- async updateMessage(tree: HostElement) {
- const payload = makeMessageUpdatePayload(tree)
-
- if (!this.message) {
- const channel = (await this.client.channels.fetch(
- this.channelId,
- )) as TextChannel
- this.message = await channel.send(payload)
- } else {
- await this.message.edit(payload)
- }
- }
-}
diff --git a/packages/reacord/library.new/core/button-shared-props.ts b/packages/reacord/library.new/core/button-shared-props.ts
deleted file mode 100644
index 4ba558f..0000000
--- a/packages/reacord/library.new/core/button-shared-props.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import type { ReactNode } from "react"
-
-/**
- * Common props between button-like components
- * @category Button
- */
-export type ButtonSharedProps = {
- /** The text on the button. Rich formatting (markdown) is not supported here. */
- label?: ReactNode
-
- /** The text on the button. Rich formatting (markdown) is not supported here.
- * If both `label` and `children` are passed, `children` will be ignored.
- */
- children?: ReactNode
-
- /** When true, the button will be slightly faded, and cannot be clicked. */
- disabled?: boolean
-
- /**
- * Renders an emoji to the left of the text.
- * Has to be a literal emoji character (e.g. 🍍),
- * or an emoji code, like `<:plus_one:778531744860602388>`.
- *
- * To get an emoji code, type your emoji in Discord chat
- * with a backslash `\` in front.
- * The bot has to be in the emoji's guild to use it.
- */
- emoji?: string
-}
diff --git a/packages/reacord/library.new/core/button.tsx b/packages/reacord/library.new/core/button.tsx
deleted file mode 100644
index bc3957b..0000000
--- a/packages/reacord/library.new/core/button.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { randomUUID } from "node:crypto"
-import React from "react"
-import type { ButtonSharedProps } from "./button-shared-props"
-import type { ComponentEvent } from "./component-event"
-import { Node } from "./node"
-import { ReacordElement } from "./reacord-element"
-
-/**
- * @category Button
- */
-export type ButtonProps = ButtonSharedProps & {
- /**
- * The style determines the color of the button and signals intent.
- * @see https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
- */
- style?: "primary" | "secondary" | "success" | "danger"
-
- /**
- * Happens when a user clicks the button.
- */
- onClick: (event: ButtonClickEvent) => void
-}
-
-/**
- * @category Button
- */
-export type ButtonClickEvent = ComponentEvent
-
-export function Button({ label, children, ...props }: ButtonProps) {
- return (
- new ButtonNode(props)}
- nodeProps={props}
- >
- {label ?? children}
-
- )
-}
-
-export class ButtonNode extends Node {
- readonly customId = randomUUID()
-}
diff --git a/packages/reacord/library.new/core/component-event.ts b/packages/reacord/library.new/core/component-event.ts
deleted file mode 100644
index 7b10665..0000000
--- a/packages/reacord/library.new/core/component-event.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import type { ReactNode } from "react"
-import type { ReacordInstance } from "./reacord-instance-pool"
-
-/**
- * @category Component Event
- */
-export type ComponentEvent = {
- /**
- * The message associated with this event.
- * For example: with a button click,
- * this is the message that the button is on.
- * @see https://discord.com/developers/docs/resources/channel#message-object
- */
- message: MessageInfo
-
- /**
- * The channel that this event occurred in.
- * @see https://discord.com/developers/docs/resources/channel#channel-object
- */
- channel: ChannelInfo
-
- /**
- * The user that triggered this event.
- * @see https://discord.com/developers/docs/resources/user#user-object
- */
- user: UserInfo
-
- /**
- * The guild that this event occurred in.
- * @see https://discord.com/developers/docs/resources/guild#guild-object
- */
- guild?: GuildInfo
-
- /**
- * Create a new reply to this event.
- */
- reply(content?: ReactNode): ReacordInstance
-
- /**
- * Create an ephemeral reply to this event,
- * shown only to the user who triggered it.
- */
- ephemeralReply(content?: ReactNode): ReacordInstance
-}
-
-/**
- * @category Component Event
- */
-export type ChannelInfo = {
- id: string
- name?: string
- topic?: string
- nsfw?: boolean
- lastMessageId?: string
- ownerId?: string
- parentId?: string
- rateLimitPerUser?: number
-}
-
-/**
- * @category Component Event
- */
-export type MessageInfo = {
- id: string
- channelId: string
- authorId: UserInfo
- member?: GuildMemberInfo
- content: string
- timestamp: string
- editedTimestamp?: string
- tts: boolean
- mentionEveryone: boolean
- /** The IDs of mentioned users */
- mentions: string[]
-}
-
-/**
- * @category Component Event
- */
-export type GuildInfo = {
- id: string
- name: string
- member: GuildMemberInfo
-}
-
-/**
- * @category Component Event
- */
-export type GuildMemberInfo = {
- id: string
- nick?: string
- displayName: string
- avatarUrl?: string
- displayAvatarUrl: string
- roles: string[]
- color: number
- joinedAt?: string
- premiumSince?: string
- pending?: boolean
- communicationDisabledUntil?: string
-}
-
-/**
- * @category Component Event
- */
-export type UserInfo = {
- id: string
- username: string
- discriminator: string
- tag: string
- avatarUrl: string
- accentColor?: number
-}
diff --git a/packages/reacord/library.new/core/make-message-payload.ts b/packages/reacord/library.new/core/make-message-payload.ts
deleted file mode 100644
index ec57ea5..0000000
--- a/packages/reacord/library.new/core/make-message-payload.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type {
- APIActionRowComponent,
- APIButtonComponent,
- RESTPostAPIChannelMessageJSONBody,
-} from "discord-api-types/v10"
-import { ButtonStyle, ComponentType } from "discord-api-types/v10"
-import type { ButtonProps } from "./button"
-import { ButtonNode } from "./button"
-import type { Node } from "./node"
-import { TextNode } from "./text-node"
-
-export type MessageUpdatePayload = RESTPostAPIChannelMessageJSONBody
-
-export function makeMessageUpdatePayload(root: Node) {
- const payload: MessageUpdatePayload = {}
-
- const content = extractText(root, 1)
- if (content) {
- payload.content = content
- }
-
- const actionRows = makeActionRows(root)
- if (actionRows.length > 0) {
- payload.components = actionRows
- }
-
- return payload
-}
-
-function makeActionRows(root: Node) {
- const actionRows: Array> = []
-
- for (const node of root.children) {
- if (node instanceof ButtonNode) {
- let currentRow = actionRows[actionRows.length - 1]
- if (!currentRow || currentRow.components.length === 5) {
- currentRow = {
- type: ComponentType.ActionRow,
- components: [],
- }
- actionRows.push(currentRow)
- }
-
- currentRow.components.push({
- type: ComponentType.Button,
- custom_id: node.customId,
- label: extractText(node, Number.POSITIVE_INFINITY),
- emoji: { name: node.props.emoji },
- style: translateButtonStyle(node.props.style ?? "secondary"),
- disabled: node.props.disabled,
- })
- }
- }
-
- return actionRows
-}
-
-function extractText(node: Node, depth: number): string {
- if (node instanceof TextNode) return node.props.text
- if (depth <= 0) return ""
- return node.children.map((child) => extractText(child, depth - 1)).join("")
-}
-
-function translateButtonStyle(style: NonNullable) {
- const styleMap = {
- primary: ButtonStyle.Primary,
- secondary: ButtonStyle.Secondary,
- danger: ButtonStyle.Danger,
- success: ButtonStyle.Success,
- } as const
- return styleMap[style]
-}
diff --git a/packages/reacord/library.new/core/node.ts b/packages/reacord/library.new/core/node.ts
deleted file mode 100644
index 985abc1..0000000
--- a/packages/reacord/library.new/core/node.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-export class Node {
- private readonly _children: Node[] = []
-
- constructor(public props: Props) {}
-
- get children(): readonly Node[] {
- return this._children
- }
-
- clear() {
- this._children.splice(0)
- }
-
- add(...nodes: Node[]) {
- this._children.push(...nodes)
- }
-
- remove(node: Node) {
- const index = this._children.indexOf(node)
- if (index !== -1) this._children.splice(index, 1)
- }
-
- insertBefore(node: Node, beforeNode: Node) {
- const index = this._children.indexOf(beforeNode)
- if (index !== -1) this._children.splice(index, 0, node)
- }
-
- clone(): this {
- const cloned: this = new (this.constructor as any)(this.props)
- cloned.add(...this.children.map((child) => child.clone()))
- return cloned
- }
-}
diff --git a/packages/reacord/library.new/core/reacord-element.ts b/packages/reacord/library.new/core/reacord-element.ts
deleted file mode 100644
index dd1ac2a..0000000
--- a/packages/reacord/library.new/core/reacord-element.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { ReactNode } from "react"
-import { createElement } from "react"
-import { inspect } from "node:util"
-import type { Node } from "./node"
-
-export function ReacordElement({
- name,
- createNode,
- nodeProps,
- children,
-}: {
- // A name representing what type of element this is,
- // so that react will know if/when it needs to recreate the node,
- // or just assign the props if the element name is the same on re-render
- name: string
- createNode: () => Node
- nodeProps: NodeProps
- children?: ReactNode
-}) {
- return createElement(
- `reacord-${name}`,
- { config: new ReacordElementConfig(createNode, nodeProps) },
- children,
- )
-}
-
-export type ReacordHostElementProps = {
- config: ReacordElementConfig
-}
-
-// Any kind of element can go through the React reconciler.
-// This class serves as a typesafe wrapper for creating a node
-// and assigning props to an existing node.
-// We can use `instanceof` to know for sure that the element is a Reacord element
-export class ReacordElementConfig {
- constructor(readonly create: () => Node, readonly props: Props) {}
-
- static parse(value: unknown): ReacordElementConfig {
- if (value instanceof ReacordElementConfig) return value
- const debugValue = inspect(value, { depth: 1 })
- throw new Error(`Expected ${ReacordElementConfig.name}, got ${debugValue}`)
- }
-}
diff --git a/packages/reacord/library.new/core/reacord-instance-pool.ts b/packages/reacord/library.new/core/reacord-instance-pool.ts
deleted file mode 100644
index f4d3d78..0000000
--- a/packages/reacord/library.new/core/reacord-instance-pool.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import type { ReactNode } from "react"
-import { Node } from "./node"
-import { reconciler } from "./reconciler"
-
-export type ReacordOptions = {
- /**
- * The max number of active instances.
- * When this limit is exceeded, the oldest instances will be disabled.
- */
- maxInstances?: number
-}
-
-export type ReacordInstance = {
- /** Render some JSX to this instance (edits the message) */
- render: (content: ReactNode) => void
-
- /** Remove this message */
- destroy: () => void
-
- /**
- * Same as destroy, but keeps the message and disables the components on it.
- * This prevents it from listening to user interactions.
- */
- deactivate: () => void
-}
-
-export type ReacordInstanceOptions = {
- initialContent: ReactNode
- renderer: ReacordMessageRenderer
-}
-
-export type ReacordMessageRenderer = {
- update: (tree: Node) => Promise
- deactivate: () => Promise
- destroy: () => Promise
-}
-
-export class ReacordInstancePool {
- private readonly options: Required
- private readonly instances = new Set()
-
- constructor({ maxInstances = 50 }: ReacordOptions) {
- this.options = { maxInstances }
- }
-
- create({ initialContent, renderer }: ReacordInstanceOptions) {
- const root = new Node({})
-
- const render = async (tree: Node) => {
- try {
- await renderer.update(tree)
- } catch (error) {
- console.error("Failed to update message.", error)
- }
- }
-
- const container = reconciler.createContainer(
- { root, render },
- 0,
- // eslint-disable-next-line unicorn/no-null
- null,
- false,
- // eslint-disable-next-line unicorn/no-null
- null,
- "reacord",
- () => {},
- // eslint-disable-next-line unicorn/no-null
- null,
- )
-
- const instance: ReacordInstance = {
- render: (content: ReactNode) => {
- reconciler.updateContainer(content, container)
- },
- deactivate: async () => {
- this.instances.delete(instance)
- try {
- await renderer.deactivate()
- } catch (error) {
- console.error("Failed to deactivate message.", error)
- }
- },
- destroy: async () => {
- this.instances.delete(instance)
- try {
- await renderer.destroy()
- } catch (error) {
- console.error("Failed to destroy message.", error)
- }
- },
- }
-
- if (initialContent !== undefined) {
- instance.render(initialContent)
- }
-
- if (this.instances.size > this.options.maxInstances) {
- ;[...this.instances][0]?.deactivate()
- }
-
- return instance
- }
-}
diff --git a/packages/reacord/library.new/core/reconciler.ts b/packages/reacord/library.new/core/reconciler.ts
deleted file mode 100644
index 86dc2b8..0000000
--- a/packages/reacord/library.new/core/reconciler.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-/* eslint-disable unicorn/prefer-modern-dom-apis */
-import ReactReconciler from "react-reconciler"
-import { DefaultEventPriority } from "react-reconciler/constants"
-import type { Node } from "./node"
-import type { ReacordHostElementProps } from "./reacord-element"
-import { ReacordElementConfig } from "./reacord-element"
-import { TextNode } from "./text-node"
-
-// technically elements of any shape can go through the reconciler,
-// so I'm typing this as unknown to ensure we validate the props
-// before using them
-type ReconcilerProps = {
- [_ in keyof ReacordHostElementProps]?: unknown
-}
-
-type ReconcilerContainer = {
- root: Node
-
- // We need to pass in a render callback, so the reconciler can tell us
- // when it's done modifying elements, after which we'll update
- // the message in Discord
- render: (root: Node) => void
-}
-
-export const reconciler = ReactReconciler<
- string, // Type
- ReconcilerProps, // Props
- ReconcilerContainer, // Container
- Node, // Instance
- TextNode, // TextInstance
- never, // SuspenseInstance
- never, // HydratableInstance
- never, // PublicInstance
- {}, // HostContext
- true, // UpdatePayload
- never, // ChildSet
- NodeJS.Timeout, // TimeoutHandle
- -1 // NoTimeout
->({
- isPrimaryRenderer: true,
- supportsMutation: true,
- supportsHydration: false,
- supportsPersistence: false,
- scheduleTimeout: setTimeout,
- cancelTimeout: clearTimeout,
- noTimeout: -1,
-
- createInstance(type, props) {
- return ReacordElementConfig.parse(props.config).create()
- },
-
- createTextInstance(text) {
- return new TextNode({ text })
- },
-
- appendInitialChild(parent, child) {
- parent.add(child)
- },
-
- appendChild(parent, child) {
- parent.add(child)
- },
-
- appendChildToContainer(container, child) {
- container.root.add(child)
- },
-
- insertBefore(parent, child, beforeChild) {
- parent.insertBefore(child, beforeChild)
- },
-
- insertInContainerBefore(container, child, beforeChild) {
- container.root.insertBefore(child, beforeChild)
- },
-
- removeChild(parent, child) {
- parent.remove(child)
- },
-
- removeChildFromContainer(container, child) {
- container.root.remove(child)
- },
-
- clearContainer(container) {
- container.root.clear()
- },
-
- commitTextUpdate(node, oldText, newText) {
- node.props.text = newText
- },
-
- commitUpdate(node, updatePayload, type, prevProps, nextProps) {
- node.props = ReacordElementConfig.parse(nextProps.config).props
- },
-
- prepareForCommit() {
- // eslint-disable-next-line unicorn/no-null
- return null
- },
-
- resetAfterCommit(container) {
- container.render(container.root.clone())
- },
-
- finalizeInitialChildren() {
- return false
- },
-
- prepareUpdate() {
- return true
- },
-
- shouldSetTextContent() {
- return false
- },
-
- getRootHostContext() {
- return {}
- },
-
- getChildHostContext() {
- return {}
- },
-
- getPublicInstance() {
- throw new Error("Refs are not supported")
- },
-
- preparePortalMount() {},
-
- getCurrentEventPriority() {
- return DefaultEventPriority
- },
-
- getInstanceFromNode() {
- return undefined
- },
-
- beforeActiveInstanceBlur() {},
- afterActiveInstanceBlur() {},
- prepareScopeUpdate() {},
- getInstanceFromScope() {
- // eslint-disable-next-line unicorn/no-null
- return null
- },
- detachDeletedInstance() {},
-})
diff --git a/packages/reacord/library.new/core/text-node.ts b/packages/reacord/library.new/core/text-node.ts
deleted file mode 100644
index 165a626..0000000
--- a/packages/reacord/library.new/core/text-node.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { Node } from "./node"
-
-export class TextNode extends Node<{ text: string }> {}
diff --git a/packages/reacord/library.new/djs/channel-message-renderer.ts b/packages/reacord/library.new/djs/channel-message-renderer.ts
deleted file mode 100644
index 1617a31..0000000
--- a/packages/reacord/library.new/djs/channel-message-renderer.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { AsyncQueue } from "@reacord/helpers/async-queue"
-import type { Client, Message, TextBasedChannel } from "discord.js"
-import { makeMessageUpdatePayload } from "../core/make-message-payload.js"
-import type { Node } from "../core/node.js"
-import type { ReacordMessageRenderer } from "../core/reacord-instance-pool.js"
-
-export class ChannelMessageRenderer implements ReacordMessageRenderer {
- private message: Message | undefined
- private channel: TextBasedChannel | undefined
- private active = true
- private readonly queue = new AsyncQueue()
-
- constructor(
- private readonly client: Client,
- private readonly channelId: string,
- ) {}
-
- update(root: Node) {
- return this.queue.add(async () => {
- const { content, embeds, components } = makeMessageUpdatePayload(root)
-
- if (!this.active) {
- return
- }
-
- if (this.message) {
- await this.message.edit({ content, embeds, components })
- return
- }
-
- const channel = await this.getChannel()
- this.message = await channel.send({ content, embeds, components })
- })
- }
-
- destroy() {
- return this.queue.add(async () => {
- this.active = false
- await this.message?.delete()
- })
- }
-
- deactivate() {
- return this.queue.add(async () => {
- this.active = false
- // TODO: disable message components
- })
- }
-
- private async getChannel() {
- if (this.channel) {
- return this.channel
- }
-
- const channel =
- this.client.channels.cache.get(this.channelId) ??
- (await this.client.channels.fetch(this.channelId))
-
- if (!channel) {
- throw new Error(`Channel ${this.channelId} not found`)
- }
- if (!channel.isTextBased()) {
- throw new Error(`Channel ${this.channelId} is not a text channel`)
- }
- return (this.channel = channel)
- }
-}
diff --git a/packages/reacord/library.new/djs/reacord-discord-js.ts b/packages/reacord/library.new/djs/reacord-discord-js.ts
deleted file mode 100644
index f6be369..0000000
--- a/packages/reacord/library.new/djs/reacord-discord-js.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Client, Interaction } from "discord.js"
-import type { ReactNode } from "react"
-import type { ReacordOptions } from "../core/reacord-instance-pool"
-import { ReacordInstancePool } from "../core/reacord-instance-pool"
-import { ChannelMessageRenderer } from "./channel-message-renderer"
-
-export class ReacordDiscordJs {
- private instances
-
- constructor(private readonly client: Client, options: ReacordOptions = {}) {
- this.instances = new ReacordInstancePool(options)
- }
-
- send(channelId: string, initialContent?: ReactNode) {
- const renderer = new ChannelMessageRenderer(this.client, channelId)
- return this.instances.create({ initialContent, renderer })
- }
-
- reply(interaction: Interaction, initialContent?: ReactNode) {}
-
- ephemeralReply(interaction: Interaction, initialContent?: ReactNode) {}
-}
diff --git a/packages/reacord/library.new/main.ts b/packages/reacord/library.new/main.ts
deleted file mode 100644
index 1cc401f..0000000
--- a/packages/reacord/library.new/main.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export { Button, type ButtonProps } from "./core/button"
-export { type ButtonSharedProps } from "./core/button-shared-props"
-export {
- type ReacordInstance,
- type ReacordOptions,
-} from "./core/reacord-instance-pool"
-export { ReacordDiscordJs } from "./djs/reacord-discord-js"
diff --git a/packages/reacord/library/components/embed-child.ts b/packages/reacord/library/components/embed-child.ts
deleted file mode 100644
index f0249b7..0000000
--- a/packages/reacord/library/components/embed-child.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Node } from "../internal/node.js"
-import type { EmbedOptions } from "./embed-options"
-
-export abstract class EmbedChildNode extends Node {
- abstract modifyEmbedOptions(options: EmbedOptions): void
-}
diff --git a/packages/reacord/library/components/embed-options.ts b/packages/reacord/library/components/embed-options.ts
deleted file mode 100644
index 91170fd..0000000
--- a/packages/reacord/library/components/embed-options.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { Except, SnakeCasedPropertiesDeep } from "type-fest"
-import type { EmbedProps } from "./embed"
-
-export type EmbedOptions = SnakeCasedPropertiesDeep<
- Except & {
- timestamp?: string
- }
->
diff --git a/packages/reacord/library/core/component-event.ts b/packages/reacord/library/core/component-event.ts
deleted file mode 100644
index c877708..0000000
--- a/packages/reacord/library/core/component-event.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { ReactNode } from "react"
-import type { ReacordInstance } from "./instance"
-
-/**
- * @category Component Event
- */
-export type ComponentEvent = {
- /**
- * Create a new reply to this event.
- */
- reply(content?: ReactNode): ReacordInstance
-
- /**
- * Create an ephemeral reply to this event,
- * shown only to the user who triggered it.
- */
- ephemeralReply(content?: ReactNode): ReacordInstance
-}
diff --git a/packages/reacord/library/core/instance.ts b/packages/reacord/library/core/instance.ts
deleted file mode 100644
index d0aa740..0000000
--- a/packages/reacord/library/core/instance.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { ReactNode } from "react"
-
-/**
- * Represents an interactive message, which can later be replaced or deleted.
- * @category Core
- */
-export type ReacordInstance = {
- /** Render some JSX to this instance (edits the message) */
- render: (content: ReactNode) => void
-
- /** Remove this message */
- destroy: () => void
-
- /**
- * Same as destroy, but keeps the message and disables the components on it.
- * This prevents it from listening to user interactions.
- */
- deactivate: () => void
-}
diff --git a/packages/reacord/library/core/reacord.tsx b/packages/reacord/library/core/reacord.tsx
deleted file mode 100644
index 1dfede6..0000000
--- a/packages/reacord/library/core/reacord.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import type { ReactNode } from "react"
-import React from "react"
-import type { ComponentInteraction } from "../internal/interaction"
-import { reconciler } from "../internal/reconciler.js"
-import type { Renderer } from "../internal/renderers/renderer"
-import type { ReacordInstance } from "./instance"
-import { InstanceProvider } from "./instance-context"
-
-/**
- * @category Core
- */
-export type ReacordConfig = {
- /**
- * The max number of active instances.
- * When this limit is exceeded, the oldest instances will be disabled.
- */
- maxInstances?: number
-}
-
-/**
- * The main Reacord class that other Reacord adapters should extend.
- * Only use this directly if you're making [a custom adapter](/guides/custom-adapters).
- */
-export abstract class Reacord {
- private renderers: Renderer[] = []
-
- constructor(private readonly config: ReacordConfig = {}) {}
-
- abstract send(...args: unknown[]): ReacordInstance
- abstract reply(...args: unknown[]): ReacordInstance
- abstract ephemeralReply(...args: unknown[]): ReacordInstance
-
- protected handleComponentInteraction(interaction: ComponentInteraction) {
- for (const renderer of this.renderers) {
- if (renderer.handleComponentInteraction(interaction)) return
- }
- }
-
- private get maxInstances() {
- return this.config.maxInstances ?? 50
- }
-
- protected createInstance(renderer: Renderer, initialContent?: ReactNode) {
- if (this.renderers.length > this.maxInstances) {
- this.deactivate(this.renderers[0]!)
- }
-
- this.renderers.push(renderer)
-
- const container = reconciler.createContainer(
- renderer,
- 0,
- // eslint-disable-next-line unicorn/no-null
- null,
- false,
- // eslint-disable-next-line unicorn/no-null
- null,
- "reacord",
- () => {},
- // eslint-disable-next-line unicorn/no-null
- null,
- )
-
- const instance: ReacordInstance = {
- render: (content: ReactNode) => {
- reconciler.updateContainer(
- {content},
- container,
- )
- },
- deactivate: () => {
- this.deactivate(renderer)
- },
- destroy: () => {
- this.renderers = this.renderers.filter((it) => it !== renderer)
- renderer.destroy()
- },
- }
-
- if (initialContent !== undefined) {
- instance.render(initialContent)
- }
-
- return instance
- }
-
- private deactivate(renderer: Renderer) {
- this.renderers = this.renderers.filter((it) => it !== renderer)
- renderer.deactivate()
- }
-}
diff --git a/packages/reacord/library/djs/reacord-discord-js.ts b/packages/reacord/library/djs/reacord-discord-js.ts
deleted file mode 100644
index a509966..0000000
--- a/packages/reacord/library/djs/reacord-discord-js.ts
+++ /dev/null
@@ -1,311 +0,0 @@
-/* eslint-disable class-methods-use-this */
-import { raise } from "@reacord/helpers/raise"
-import type {
- APIMessageComponentButtonInteraction,
- APIMessageComponentSelectMenuInteraction,
-} from "discord.js"
-import * as Discord from "discord.js"
-import type { ReactNode } from "react"
-
-import type { ReacordInstance } from "../core/instance"
-import type { ReacordConfig } from "../core/reacord"
-import { Reacord } from "../core/reacord"
-import type { ComponentInteraction } from "../internal/interaction"
-import type {
- Message,
- MessageButtonOptions,
- MessageOptions,
-} from "../internal/message"
-import { ChannelMessageRenderer } from "../internal/renderers/channel-message-renderer"
-import { InteractionReplyRenderer } from "../internal/renderers/interaction-reply-renderer"
-
-/**
- * The Reacord adapter for Discord.js.
- * @category Core
- */
-export class ReacordDiscordJs extends Reacord {
- constructor(private client: Discord.Client, config: ReacordConfig = {}) {
- super(config)
-
- client.on("interactionCreate", (interaction) => {
- if (interaction.isButton() || interaction.isSelectMenu()) {
- this.handleComponentInteraction(
- this.createReacordComponentInteraction(interaction),
- )
- }
- })
-
- client.ws.on(
- Discord.GatewayDispatchEvents.InteractionCreate,
- (data: Discord.APIInteraction) => {
- if (data.type === Discord.InteractionType.MessageComponent) {
- data
- // this.handleComponentInteraction(
- // this.createReacordComponentInteraction(data),
- // )
- }
- },
- )
- }
-
- /**
- * Sends a message to a channel.
- * @see https://reacord.mapleleaf.dev/guides/sending-messages
- */
- override send(
- channelId: string,
- initialContent?: React.ReactNode,
- ): ReacordInstance {
- return this.createInstance(
- this.createChannelRenderer(channelId),
- initialContent,
- )
- }
-
- /**
- * Sends a message as a reply to a command interaction.
- * @see https://reacord.mapleleaf.dev/guides/sending-messages
- */
- override reply(
- interaction: Discord.CommandInteraction,
- initialContent?: React.ReactNode,
- ): ReacordInstance {
- return this.createInstance(
- this.createInteractionReplyRenderer(interaction),
- initialContent,
- )
- }
-
- /**
- * Sends an ephemeral message as a reply to a command interaction.
- * @see https://reacord.mapleleaf.dev/guides/sending-messages
- */
- override ephemeralReply(
- interaction: Discord.CommandInteraction,
- initialContent?: React.ReactNode,
- ): ReacordInstance {
- return this.createInstance(
- this.createEphemeralInteractionReplyRenderer(interaction),
- initialContent,
- )
- }
-
- private createChannelRenderer(channelId: string) {
- return new ChannelMessageRenderer({
- send: async (options) => {
- const channel =
- this.client.channels.cache.get(channelId) ??
- (await this.client.channels.fetch(channelId)) ??
- raise(`Channel ${channelId} not found`)
-
- if (!channel.isTextBased()) {
- raise(`Channel ${channelId} is not a text channel`)
- }
-
- const message = await channel.send(getDiscordMessageOptions(options))
- return createReacordMessage(message)
- },
- })
- }
-
- private createInteractionReplyRenderer(
- interaction:
- | Discord.CommandInteraction
- | Discord.MessageComponentInteraction,
- ) {
- return new InteractionReplyRenderer({
- type: "command",
- id: interaction.id,
- reply: async (options) => {
- const message = await interaction.reply({
- ...getDiscordMessageOptions(options),
- fetchReply: true,
- })
- return createReacordMessage(message as Discord.Message)
- },
- followUp: async (options) => {
- const message = await interaction.followUp({
- ...getDiscordMessageOptions(options),
- fetchReply: true,
- })
- return createReacordMessage(message as Discord.Message)
- },
- })
- }
-
- private createEphemeralInteractionReplyRenderer(
- interaction:
- | Discord.CommandInteraction
- | Discord.MessageComponentInteraction,
- ) {
- return new InteractionReplyRenderer({
- type: "command",
- id: interaction.id,
- reply: async (options) => {
- await interaction.reply({
- ...getDiscordMessageOptions(options),
- ephemeral: true,
- })
- return createEphemeralReacordMessage()
- },
- followUp: async (options) => {
- await interaction.followUp({
- ...getDiscordMessageOptions(options),
- ephemeral: true,
- })
- return createEphemeralReacordMessage()
- },
- })
- }
-
- private createReacordComponentInteraction(
- interaction: Discord.MessageComponentInteraction,
- ): ComponentInteraction {
- const baseProps = {
- id: interaction.id,
- customId: interaction.customId,
- update: async (options: MessageOptions) => {
- await interaction.update(getDiscordMessageOptions(options))
- },
- deferUpdate: async () => {
- if (interaction.replied || interaction.deferred) return
- await interaction.deferUpdate()
- },
- reply: async (options: MessageOptions) => {
- const message = await interaction.reply({
- ...getDiscordMessageOptions(options),
- fetchReply: true,
- })
- return createReacordMessage(message as Discord.Message)
- },
- followUp: async (options: MessageOptions) => {
- const message = await interaction.followUp({
- ...getDiscordMessageOptions(options),
- fetchReply: true,
- })
- return createReacordMessage(message as Discord.Message)
- },
- event: {
- reply: (content?: ReactNode) =>
- this.createInstance(
- this.createInteractionReplyRenderer(interaction),
- content,
- ),
-
- ephemeralReply: (content: ReactNode) =>
- this.createInstance(
- this.createEphemeralInteractionReplyRenderer(interaction),
- content,
- ),
- },
- }
-
- if (interaction.isButton()) {
- return {
- ...baseProps,
- type: "button",
- event: {
- ...baseProps.event,
- interaction:
- interaction.toJSON() as APIMessageComponentButtonInteraction,
- },
- }
- }
-
- if (interaction.isSelectMenu()) {
- return {
- ...baseProps,
- type: "select",
- event: {
- ...baseProps.event,
- values: interaction.values,
- interaction:
- interaction.toJSON() as APIMessageComponentSelectMenuInteraction,
- },
- }
- }
-
- raise(`Unsupported component interaction type: ${interaction.type}`)
- }
-}
-
-function createReacordMessage(message: Discord.Message): Message {
- return {
- edit: async (options) => {
- await message.edit(getDiscordMessageOptions(options))
- },
- delete: async () => {
- await message.delete()
- },
- }
-}
-
-function createEphemeralReacordMessage(): Message {
- return {
- edit: () => {
- console.warn("Ephemeral messages can't be edited")
- return Promise.resolve()
- },
- delete: () => {
- console.warn("Ephemeral messages can't be deleted")
- return Promise.resolve()
- },
- }
-}
-
-function convertButtonStyleToEnum(style: MessageButtonOptions["style"]) {
- const styleMap = {
- primary: Discord.ButtonStyle.Primary,
- secondary: Discord.ButtonStyle.Secondary,
- success: Discord.ButtonStyle.Success,
- danger: Discord.ButtonStyle.Danger,
- } as const
-
- return styleMap[style ?? "secondary"]
-}
-
-// TODO: this could be a part of the core library,
-// and also handle some edge cases, e.g. empty messages
-function getDiscordMessageOptions(reacordOptions: MessageOptions) {
- const options = {
- // eslint-disable-next-line unicorn/no-null
- content: reacordOptions.content || null,
- embeds: reacordOptions.embeds,
- components: reacordOptions.actionRows.map((row) => ({
- type: Discord.ComponentType.ActionRow,
- components: row.map(
- (component): Discord.MessageActionRowComponentData => {
- if (component.type === "button") {
- return {
- type: Discord.ComponentType.Button,
- customId: component.customId,
- label: component.label ?? "",
- style: convertButtonStyleToEnum(component.style),
- disabled: component.disabled,
- emoji: component.emoji,
- }
- }
-
- if (component.type === "select") {
- return {
- ...component,
- type: Discord.ComponentType.SelectMenu,
- options: component.options.map((option) => ({
- ...option,
- default: component.values?.includes(option.value),
- })),
- }
- }
-
- raise(`Unsupported component type: ${component.type}`)
- },
- ),
- })),
- }
-
- if (!options.content && !options.embeds?.length) {
- options.content = "_ _"
- }
-
- return options
-}
diff --git a/packages/reacord/library/internal/channel.ts b/packages/reacord/library/internal/channel.ts
deleted file mode 100644
index b574496..0000000
--- a/packages/reacord/library/internal/channel.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import type { Message, MessageOptions } from "./message"
-
-export type Channel = {
- send(message: MessageOptions): Promise
-}
diff --git a/packages/reacord/library/internal/container.ts b/packages/reacord/library/internal/container.ts
deleted file mode 100644
index 8941fcd..0000000
--- a/packages/reacord/library/internal/container.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-export class Container {
- private items: T[] = []
-
- add(...items: T[]) {
- this.items.push(...items)
- }
-
- addBefore(item: T, before: T) {
- let index = this.items.indexOf(before)
- if (index === -1) {
- index = this.items.length
- }
- this.items.splice(index, 0, item)
- }
-
- remove(toRemove: T) {
- this.items = this.items.filter((item) => item !== toRemove)
- }
-
- clear() {
- this.items = []
- }
-
- find(predicate: (item: T) => boolean): T | undefined {
- return this.items.find(predicate)
- }
-
- findType(type: new (...args: any[]) => U): U | undefined {
- for (const item of this.items) {
- if (item instanceof type) return item
- }
- }
-
- [Symbol.iterator]() {
- return this.items[Symbol.iterator]()
- }
-}
diff --git a/packages/reacord/library/internal/interaction.ts b/packages/reacord/library/internal/interaction.ts
deleted file mode 100644
index 06c2daf..0000000
--- a/packages/reacord/library/internal/interaction.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { ComponentEvent } from "../core/component-event"
-import type { ButtonClickEvent, SelectChangeEvent } from "../main"
-import type { Message, MessageOptions } from "./message"
-
-export type Interaction = CommandInteraction | ComponentInteraction
-export type ComponentInteraction = ButtonInteraction | SelectInteraction
-
-export type CommandInteraction = BaseInteraction<"command">
-
-export type ButtonInteraction = BaseComponentInteraction<
- "button",
- ButtonClickEvent
->
-
-export type SelectInteraction = BaseComponentInteraction<
- "select",
- SelectChangeEvent
->
-
-export type BaseInteraction = {
- type: Type
- id: string
- reply(messageOptions: MessageOptions): Promise
- followUp(messageOptions: MessageOptions): Promise
-}
-
-export type BaseComponentInteraction<
- Type extends string,
- Event extends ComponentEvent,
-> = BaseInteraction & {
- event: Event
- customId: string
- update(options: MessageOptions): Promise
- deferUpdate(): Promise
-}
diff --git a/packages/reacord/library/internal/limited-collection.ts b/packages/reacord/library/internal/limited-collection.ts
deleted file mode 100644
index 2150d3f..0000000
--- a/packages/reacord/library/internal/limited-collection.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export class LimitedCollection {
- private items: T[] = []
-
- constructor(private readonly size: number) {}
-
- add(item: T) {
- if (this.items.length >= this.size) {
- this.items.shift()
- }
- this.items.push(item)
- }
-
- has(item: T) {
- return this.items.includes(item)
- }
-
- values(): readonly T[] {
- return this.items
- }
-
- [Symbol.iterator]() {
- return this.items[Symbol.iterator]()
- }
-}
diff --git a/packages/reacord/library/internal/message.ts b/packages/reacord/library/internal/message.ts
deleted file mode 100644
index 89b5e70..0000000
--- a/packages/reacord/library/internal/message.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { last } from "@reacord/helpers/last"
-import type { Except } from "type-fest"
-import type { EmbedOptions } from "../core/components/embed-options"
-import type { SelectProps } from "../core/components/select"
-
-export type MessageOptions = {
- content: string
- embeds: EmbedOptions[]
- actionRows: ActionRow[]
-}
-
-export type ActionRow = ActionRowItem[]
-
-export type ActionRowItem =
- | MessageButtonOptions
- | MessageLinkOptions
- | MessageSelectOptions
-
-export type MessageButtonOptions = {
- type: "button"
- customId: string
- label?: string
- style?: "primary" | "secondary" | "success" | "danger"
- disabled?: boolean
- emoji?: string
-}
-
-export type MessageLinkOptions = {
- type: "link"
- url: string
- label?: string
- emoji?: string
- disabled?: boolean
-}
-
-export type MessageSelectOptions = Except & {
- type: "select"
- customId: string
- options: MessageSelectOptionOptions[]
-}
-
-export type MessageSelectOptionOptions = {
- label: string
- value: string
- description?: string
- emoji?: string
-}
-
-export type Message = {
- edit(options: MessageOptions): Promise
- delete(): Promise
-}
-
-export function getNextActionRow(options: MessageOptions): ActionRow {
- let actionRow = last(options.actionRows)
- if (
- actionRow == undefined ||
- actionRow.length >= 5 ||
- actionRow[0]?.type === "select"
- ) {
- actionRow = []
- options.actionRows.push(actionRow)
- }
- return actionRow
-}
diff --git a/packages/reacord/library/internal/renderers/channel-message-renderer.ts b/packages/reacord/library/internal/renderers/channel-message-renderer.ts
deleted file mode 100644
index 32fafe1..0000000
--- a/packages/reacord/library/internal/renderers/channel-message-renderer.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { Channel } from "../channel"
-import type { Message, MessageOptions } from "../message"
-import { Renderer } from "./renderer"
-
-export class ChannelMessageRenderer extends Renderer {
- constructor(private channel: Channel) {
- super()
- }
-
- protected createMessage(options: MessageOptions): Promise {
- return this.channel.send(options)
- }
-}
diff --git a/packages/reacord/library/internal/renderers/interaction-reply-renderer.ts b/packages/reacord/library/internal/renderers/interaction-reply-renderer.ts
deleted file mode 100644
index 163c78a..0000000
--- a/packages/reacord/library/internal/renderers/interaction-reply-renderer.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Interaction } from "../interaction"
-import type { Message, MessageOptions } from "../message"
-import { Renderer } from "./renderer"
-
-// keep track of interaction ids which have replies,
-// so we know whether to call reply() or followUp()
-const repliedInteractionIds = new Set()
-
-export class InteractionReplyRenderer extends Renderer {
- constructor(private interaction: Interaction) {
- super()
- }
-
- protected createMessage(options: MessageOptions): Promise {
- if (repliedInteractionIds.has(this.interaction.id)) {
- return this.interaction.followUp(options)
- }
-
- repliedInteractionIds.add(this.interaction.id)
- return this.interaction.reply(options)
- }
-}
diff --git a/packages/reacord/library/internal/renderers/renderer.ts b/packages/reacord/library/internal/renderers/renderer.ts
deleted file mode 100644
index fb9146d..0000000
--- a/packages/reacord/library/internal/renderers/renderer.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { Subject } from "rxjs"
-import { concatMap } from "rxjs/operators"
-import { Container } from "../container.js"
-import type { ComponentInteraction } from "../interaction"
-import type { Message, MessageOptions } from "../message"
-import type { Node } from "../node.js"
-
-type UpdatePayload =
- | { action: "update" | "deactivate"; options: MessageOptions }
- | { action: "deferUpdate"; interaction: ComponentInteraction }
- | { action: "destroy" }
-
-export abstract class Renderer {
- readonly nodes = new Container>()
- private componentInteraction?: ComponentInteraction
- private message?: Message
- private active = true
- private updates = new Subject()
-
- private updateSubscription = this.updates
- .pipe(concatMap((payload) => this.updateMessage(payload)))
- .subscribe({ error: console.error })
-
- render() {
- if (!this.active) {
- console.warn("Attempted to update a deactivated message")
- return
- }
-
- this.updates.next({
- options: this.getMessageOptions(),
- action: "update",
- })
- }
-
- deactivate() {
- this.active = false
- this.updates.next({
- options: this.getMessageOptions(),
- action: "deactivate",
- })
- }
-
- destroy() {
- this.active = false
- this.updates.next({ action: "destroy" })
- }
-
- handleComponentInteraction(interaction: ComponentInteraction) {
- this.componentInteraction = interaction
-
- setTimeout(() => {
- this.updates.next({ action: "deferUpdate", interaction })
- }, 500)
-
- for (const node of this.nodes) {
- if (node.handleComponentInteraction(interaction)) {
- return true
- }
- }
- }
-
- protected abstract createMessage(options: MessageOptions): Promise
-
- private getMessageOptions(): MessageOptions {
- const options: MessageOptions = {
- content: "",
- embeds: [],
- actionRows: [],
- }
- for (const node of this.nodes) {
- node.modifyMessageOptions(options)
- }
- return options
- }
-
- private async updateMessage(payload: UpdatePayload) {
- if (payload.action === "destroy") {
- this.updateSubscription.unsubscribe()
- await this.message?.delete()
- return
- }
-
- if (payload.action === "deactivate") {
- this.updateSubscription.unsubscribe()
-
- await this.message?.edit({
- ...payload.options,
- actionRows: payload.options.actionRows.map((row) =>
- row.map((component) => ({
- ...component,
- disabled: true,
- })),
- ),
- })
-
- return
- }
-
- if (payload.action === "deferUpdate") {
- await payload.interaction.deferUpdate()
- return
- }
-
- if (this.componentInteraction) {
- const promise = this.componentInteraction.update(payload.options)
- this.componentInteraction = undefined
- await promise
- return
- }
-
- if (this.message) {
- await this.message.edit(payload.options)
- return
- }
-
- this.message = await this.createMessage(payload.options)
- }
-}
diff --git a/packages/reacord/library/internal/timeout.ts b/packages/reacord/library/internal/timeout.ts
deleted file mode 100644
index c3b178c..0000000
--- a/packages/reacord/library/internal/timeout.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export class Timeout {
- private timeoutId?: NodeJS.Timeout
-
- constructor(
- private readonly time: number,
- private readonly callback: () => void,
- ) {}
-
- run() {
- this.cancel()
- this.timeoutId = setTimeout(this.callback, this.time)
- }
-
- cancel() {
- if (this.timeoutId) {
- clearTimeout(this.timeoutId)
- this.timeoutId = undefined
- }
- }
-}
diff --git a/packages/reacord/library/make-message-update-payload.ts b/packages/reacord/library/make-message-update-payload.ts
index 08c9e00..2881175 100644
--- a/packages/reacord/library/make-message-update-payload.ts
+++ b/packages/reacord/library/make-message-update-payload.ts
@@ -6,28 +6,28 @@ import type {
APISelectMenuOption,
} from "discord-api-types/v10"
import { ButtonStyle, ComponentType } from "discord-api-types/v10"
-import { ActionRowNode } from "./components/action-row"
-import type { ButtonProps } from "./components/button"
-import { ButtonNode } from "./components/button"
-import { EmbedNode } from "./components/embed"
-import { EmbedAuthorNode } from "./components/embed-author"
+import type { Node } from "./node"
+import { ActionRowNode } from "./react/action-row"
+import type { ButtonProps } from "./react/button"
+import { ButtonNode } from "./react/button"
+import { EmbedNode } from "./react/embed"
+import { EmbedAuthorNode } from "./react/embed-author"
import {
EmbedFieldNameNode,
EmbedFieldNode,
EmbedFieldValueNode,
-} from "./components/embed-field"
-import { EmbedFooterNode } from "./components/embed-footer"
-import { EmbedImageNode } from "./components/embed-image"
-import { EmbedThumbnailNode } from "./components/embed-thumbnail"
-import { EmbedTitleNode } from "./components/embed-title"
-import { LinkNode } from "./components/link"
+} from "./react/embed-field"
+import { EmbedFooterNode } from "./react/embed-footer"
+import { EmbedImageNode } from "./react/embed-image"
+import { EmbedThumbnailNode } from "./react/embed-thumbnail"
+import { EmbedTitleNode } from "./react/embed-title"
+import { LinkNode } from "./react/link"
import {
OptionDescriptionNode,
OptionLabelNode,
OptionNode,
-} from "./components/option"
-import { SelectNode } from "./components/select"
-import type { Node } from "./node"
+} from "./react/option"
+import { SelectNode } from "./react/select"
export type MessageUpdatePayload = {
content: string
diff --git a/packages/reacord/library/reacord-client.ts b/packages/reacord/library/reacord-client.ts
index 5f8a65d..1d965e8 100644
--- a/packages/reacord/library/reacord-client.ts
+++ b/packages/reacord/library/reacord-client.ts
@@ -6,9 +6,9 @@ import {
InteractionType,
} from "discord.js"
import * as React from "react"
-import { InstanceProvider } from "./core/instance-context"
import type { ReacordInstance } from "./reacord-instance.js"
import { ReacordInstancePrivate } from "./reacord-instance.js"
+import { InstanceProvider } from "./react/instance-context"
import type { Renderer } from "./renderer.js"
import {
ChannelMessageRenderer,
diff --git a/packages/reacord/library/reacord-instance.ts b/packages/reacord/library/reacord-instance.ts
index db170fa..80bb1f9 100644
--- a/packages/reacord/library/reacord-instance.ts
+++ b/packages/reacord/library/reacord-instance.ts
@@ -4,13 +4,13 @@ import type {
APIMessageComponentSelectMenuInteraction,
} from "discord.js"
import { ComponentType } from "discord.js"
-import { ButtonNode } from "./components/button"
-import type { SelectChangeEvent } from "./components/select"
-import { SelectNode } from "./components/select"
-import type { ComponentEvent } from "./core/component-event"
import { Node } from "./node"
import type { ReacordClient } from "./reacord-client"
-import { reconciler } from "./reconciler"
+import { ButtonNode } from "./react/button"
+import type { ComponentEvent } from "./react/component-event"
+import { reconciler } from "./react/reconciler"
+import type { SelectChangeEvent } from "./react/select"
+import { SelectNode } from "./react/select"
import type { Renderer } from "./renderer"
/**
diff --git a/packages/reacord/library/components/action-row.tsx b/packages/reacord/library/react/action-row.tsx
similarity index 94%
rename from packages/reacord/library/components/action-row.tsx
rename to packages/reacord/library/react/action-row.tsx
index e16a3ca..4f465b7 100644
--- a/packages/reacord/library/components/action-row.tsx
+++ b/packages/reacord/library/react/action-row.tsx
@@ -1,7 +1,7 @@
import type { ReactNode } from "react"
import React from "react"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* Props for an action row
diff --git a/packages/reacord/library/components/button-shared-props.ts b/packages/reacord/library/react/button-shared-props.ts
similarity index 100%
rename from packages/reacord/library/components/button-shared-props.ts
rename to packages/reacord/library/react/button-shared-props.ts
diff --git a/packages/reacord/library/components/button.tsx b/packages/reacord/library/react/button.tsx
similarity index 94%
rename from packages/reacord/library/components/button.tsx
rename to packages/reacord/library/react/button.tsx
index e70b62e..3dccb7f 100644
--- a/packages/reacord/library/components/button.tsx
+++ b/packages/reacord/library/react/button.tsx
@@ -1,10 +1,10 @@
import type { APIMessageComponentButtonInteraction } from "discord.js"
import { randomUUID } from "node:crypto"
import React from "react"
-import type { ComponentEvent } from "../core/component-event.js"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
import type { ButtonSharedProps } from "./button-shared-props"
+import type { ComponentEvent } from "./component-event.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Button
diff --git a/packages/reacord/library.new.new/core/component-event.ts b/packages/reacord/library/react/component-event.ts
similarity index 85%
rename from packages/reacord/library.new.new/core/component-event.ts
rename to packages/reacord/library/react/component-event.ts
index 0dc8b4c..3cf8f36 100644
--- a/packages/reacord/library.new.new/core/component-event.ts
+++ b/packages/reacord/library/react/component-event.ts
@@ -1,5 +1,5 @@
import type { ReactNode } from "react"
-import type { ReacordInstance } from "./reacord-instance"
+import type { ReacordInstance } from "../reacord-instance.js"
/**
* @category Component Event
diff --git a/packages/reacord/library/components/embed-author.tsx b/packages/reacord/library/react/embed-author.tsx
similarity index 90%
rename from packages/reacord/library/components/embed-author.tsx
rename to packages/reacord/library/react/embed-author.tsx
index 450715b..6a15751 100644
--- a/packages/reacord/library/components/embed-author.tsx
+++ b/packages/reacord/library/react/embed-author.tsx
@@ -1,7 +1,7 @@
import type { ReactNode } from "react"
import React from "react"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Embed
diff --git a/packages/reacord/library/components/embed-field.tsx b/packages/reacord/library/react/embed-field.tsx
similarity index 95%
rename from packages/reacord/library/components/embed-field.tsx
rename to packages/reacord/library/react/embed-field.tsx
index 782296b..9a1c0fe 100644
--- a/packages/reacord/library/components/embed-field.tsx
+++ b/packages/reacord/library/react/embed-field.tsx
@@ -1,7 +1,7 @@
import type { ReactNode } from "react"
import React from "react"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Embed
diff --git a/packages/reacord/library/components/embed-footer.tsx b/packages/reacord/library/react/embed-footer.tsx
similarity index 94%
rename from packages/reacord/library/components/embed-footer.tsx
rename to packages/reacord/library/react/embed-footer.tsx
index 5e944dc..b5720e5 100644
--- a/packages/reacord/library/components/embed-footer.tsx
+++ b/packages/reacord/library/react/embed-footer.tsx
@@ -1,7 +1,7 @@
import type { ReactNode } from "react"
import React from "react"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Embed
diff --git a/packages/reacord/library/components/embed-image.tsx b/packages/reacord/library/react/embed-image.tsx
similarity index 87%
rename from packages/reacord/library/components/embed-image.tsx
rename to packages/reacord/library/react/embed-image.tsx
index 3906c0f..3e15e88 100644
--- a/packages/reacord/library/components/embed-image.tsx
+++ b/packages/reacord/library/react/embed-image.tsx
@@ -1,6 +1,6 @@
import React from "react"
import { Node } from "../node"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Embed
diff --git a/packages/reacord/library/components/embed-thumbnail.tsx b/packages/reacord/library/react/embed-thumbnail.tsx
similarity index 88%
rename from packages/reacord/library/components/embed-thumbnail.tsx
rename to packages/reacord/library/react/embed-thumbnail.tsx
index 4c6bec4..fe46157 100644
--- a/packages/reacord/library/components/embed-thumbnail.tsx
+++ b/packages/reacord/library/react/embed-thumbnail.tsx
@@ -1,6 +1,6 @@
import React from "react"
import { Node } from "../node"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Embed
diff --git a/packages/reacord/library/components/embed-title.tsx b/packages/reacord/library/react/embed-title.tsx
similarity index 90%
rename from packages/reacord/library/components/embed-title.tsx
rename to packages/reacord/library/react/embed-title.tsx
index 09d33be..0f8d61d 100644
--- a/packages/reacord/library/components/embed-title.tsx
+++ b/packages/reacord/library/react/embed-title.tsx
@@ -2,7 +2,7 @@ import type { ReactNode } from "react"
import React from "react"
import type { Except } from "type-fest"
import { Node } from "../node"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Embed
diff --git a/packages/reacord/library/components/embed.tsx b/packages/reacord/library/react/embed.tsx
similarity index 96%
rename from packages/reacord/library/components/embed.tsx
rename to packages/reacord/library/react/embed.tsx
index 8995953..2be2211 100644
--- a/packages/reacord/library/components/embed.tsx
+++ b/packages/reacord/library/react/embed.tsx
@@ -1,6 +1,6 @@
import React from "react"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Embed
diff --git a/packages/reacord/library/core/instance-context.tsx b/packages/reacord/library/react/instance-context.ts
similarity index 89%
rename from packages/reacord/library/core/instance-context.tsx
rename to packages/reacord/library/react/instance-context.ts
index 0e62a39..4112b06 100644
--- a/packages/reacord/library/core/instance-context.tsx
+++ b/packages/reacord/library/react/instance-context.ts
@@ -1,6 +1,6 @@
import { raise } from "@reacord/helpers/raise"
import * as React from "react"
-import type { ReacordInstance } from "./instance"
+import type { ReacordInstance } from "../reacord-instance.js"
const Context = React.createContext(undefined)
diff --git a/packages/reacord/library/components/link.tsx b/packages/reacord/library/react/link.tsx
similarity index 92%
rename from packages/reacord/library/components/link.tsx
rename to packages/reacord/library/react/link.tsx
index 81ec27f..89f3fd3 100644
--- a/packages/reacord/library/components/link.tsx
+++ b/packages/reacord/library/react/link.tsx
@@ -1,8 +1,8 @@
import React from "react"
import type { Except } from "type-fest"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
import type { ButtonSharedProps } from "./button-shared-props"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Link
diff --git a/packages/reacord/library/components/option.tsx b/packages/reacord/library/react/option.tsx
similarity index 96%
rename from packages/reacord/library/components/option.tsx
rename to packages/reacord/library/react/option.tsx
index 2af4384..16f7285 100644
--- a/packages/reacord/library/components/option.tsx
+++ b/packages/reacord/library/react/option.tsx
@@ -1,7 +1,7 @@
import type { ReactNode } from "react"
import React from "react"
import { Node } from "../node"
-import { ReacordElement } from "../reacord-element"
+import { ReacordElement } from "./reacord-element"
/**
* @category Select
diff --git a/packages/reacord/library/reacord-element.ts b/packages/reacord/library/react/reacord-element.ts
similarity index 87%
rename from packages/reacord/library/reacord-element.ts
rename to packages/reacord/library/react/reacord-element.ts
index ab5f9ac..0fd7bdd 100644
--- a/packages/reacord/library/reacord-element.ts
+++ b/packages/reacord/library/react/reacord-element.ts
@@ -1,6 +1,6 @@
import type { ReactNode } from "react"
import React from "react"
-import type { Node } from "./node"
+import type { Node } from "../node"
export function ReacordElement(props: {
props: Props
diff --git a/packages/reacord/library/reconciler.ts b/packages/reacord/library/react/reconciler.ts
similarity index 96%
rename from packages/reacord/library/reconciler.ts
rename to packages/reacord/library/react/reconciler.ts
index 57d014f..63b6f69 100644
--- a/packages/reacord/library/reconciler.ts
+++ b/packages/reacord/library/react/reconciler.ts
@@ -2,8 +2,8 @@
import { raise } from "@reacord/helpers/raise.js"
import ReactReconciler from "react-reconciler"
import { DefaultEventPriority } from "react-reconciler/constants"
-import { Node, TextNode } from "./node.js"
-import type { ReacordInstancePrivate } from "./reacord-instance.js"
+import { Node, TextNode } from "../node.js"
+import type { ReacordInstancePrivate } from "../reacord-instance.js"
export const reconciler = ReactReconciler<
string, // Type,
diff --git a/packages/reacord/library/components/select.tsx b/packages/reacord/library/react/select.tsx
similarity index 95%
rename from packages/reacord/library/components/select.tsx
rename to packages/reacord/library/react/select.tsx
index be5c6c3..e00bf38 100644
--- a/packages/reacord/library/components/select.tsx
+++ b/packages/reacord/library/react/select.tsx
@@ -2,9 +2,9 @@ import type { APIMessageComponentSelectMenuInteraction } from "discord.js"
import { randomUUID } from "node:crypto"
import type { ReactNode } from "react"
import React from "react"
-import type { ComponentEvent } from "../core/component-event.js"
import { Node } from "../node.js"
-import { ReacordElement } from "../reacord-element.js"
+import type { ComponentEvent } from "./component-event.js"
+import { ReacordElement } from "./reacord-element.js"
/**
* @category Select
diff --git a/packages/reacord/library/renderer.ts b/packages/reacord/library/renderer.ts
index 13a1a44..b4e4e07 100644
--- a/packages/reacord/library/renderer.ts
+++ b/packages/reacord/library/renderer.ts
@@ -1,4 +1,4 @@
-import { AsyncQueue } from "@reacord/helpers/async-queue"
+import { AsyncQueue } from "@reacord/helpers/async-queue.js"
import type { Client, Message } from "discord.js"
import { TextChannel } from "discord.js"
import { makeMessageUpdatePayload } from "./make-message-update-payload.js"
diff --git a/packages/reacord/scripts/generate-exports.ts b/packages/reacord/scripts/generate-exports.ts
new file mode 100644
index 0000000..e69de29
diff --git a/packages/reacord/scripts/manual-test.tsx b/packages/reacord/scripts/manual-test.tsx
index 2d179f3..07f5809 100644
--- a/packages/reacord/scripts/manual-test.tsx
+++ b/packages/reacord/scripts/manual-test.tsx
@@ -3,11 +3,11 @@ import "dotenv/config"
import { kebabCase } from "lodash-es"
import * as React from "react"
import { useState } from "react"
-import { Button } from "../library/components/button"
-import { Option } from "../library/components/option"
-import { Select } from "../library/components/select"
-import { useInstance } from "../library/core/instance-context"
import { ReacordClient } from "../library/reacord-client"
+import { Button } from "../library/react/button"
+import { useInstance } from "../library/react/instance-context"
+import { Option } from "../library/react/option"
+import { Select } from "../library/react/select"
const client = new Client({ intents: IntentsBitField.Flags.Guilds })