@seedcord/http 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,177 @@
1
+ import { A as Emojis, C as MessageContextMenuHandler, D as InteractionHandler, E as InteractionMiddleware, M as ModalFields, N as Plugin, O as RepliableHandler, S as AutocompleteHandler, T as SlashHandler, _ as StringMenuHandler, a as HmrModuleHandler, b as ButtonHandler, c as ArraySource, d as Subscriber, f as WebhookLog, g as SelectMenuHandler, h as RoleMenuHandler, i as HttpStartupTasks, j as installEmojis, k as ReplySender, l as CursorSource, m as MentionableMenuHandler, n as InteractionsService, o as version, p as ChannelMenuHandler, r as rejectNonPost, s as Gated, t as usernameOf, u as Paginator, v as UserMenuHandler, w as UserContextMenuHandler, x as ComponentHandler, y as ModalHandler } from "./username-B6enaS_x.mjs";
2
+ import { BAKED_IDS_KEY, CoordinatedStartup, HmrManager, PluginLifecycle, attachmentsOf, bindBotColor, connectDevChannel, forwardLogsToCli, installLinks, sealAttachments, toWireError } from "@seedcord/core/internal";
3
+ import { PluginHost } from "@seedcord/core/plugin";
4
+ import { SeedcordErrorCode } from "@seedcord/errors";
5
+ import { SeedcordError, setColorLevel } from "@seedcord/errors/internal";
6
+ import { Logger } from "@seedcord/logger";
7
+ import { HostAugmentTarget, HostVersion, SeedcordBrand } from "@seedcord/types/internal";
8
+
9
+ export * from "@seedcord/errors"
10
+
11
+ export * from "@seedcord/core"
12
+
13
+ export * from "@seedcord/logger"
14
+
15
+ export * from "@seedcord/event-emitter"
16
+
17
+ export * from "@seedcord/rate-limiter"
18
+
19
+ export * from "@seedcord/utils"
20
+
21
+ //#region src/edge/defaults.ts
22
+ const edgeShutdown = { addTask: () => {
23
+ throw new SeedcordError(SeedcordErrorCode.CoreLifecycleUnavailable);
24
+ } };
25
+ const EDGE_SWEEPERS = {
26
+ hashSweepInterval: 0,
27
+ handlerSweepInterval: 0
28
+ };
29
+ function edgeRestOptions(given = {}) {
30
+ for (const key of Object.keys(EDGE_SWEEPERS)) if (given[key] !== void 0) throw new SeedcordError(SeedcordErrorCode.ConfigEdgeRestSweeper, [key]);
31
+ return {
32
+ ...given,
33
+ ...EDGE_SWEEPERS
34
+ };
35
+ }
36
+
37
+ //#endregion
38
+ //#region src/edge/Seedcord.ts
39
+ function bakedIds() {
40
+ const ids = Reflect.get(globalThis, Symbol.for(BAKED_IDS_KEY));
41
+ if (!ids) throw new SeedcordError(SeedcordErrorCode.CoreEdgeUnbuilt);
42
+ return ids;
43
+ }
44
+ /**
45
+ * The HTTP-interactions bot on Cloudflare Workers. Default-export it from `bot.ts`. Cloudflare
46
+ * calls `fetch` for every request.
47
+ *
48
+ * The constructor stores the config. The first POST in each isolate reads `DISCORD_BOT_TOKEN`
49
+ * and the public key, command ids, and emoji ids that `seedcord build` wrote into the worker. It
50
+ * loads the handler and subscriber folders, then runs the startup tasks and every plugin's `init()`
51
+ * and `ready()`. Requests that arrive meanwhile wait for it.
52
+ *
53
+ * Under `seedcord dev` the bot starts when the CLI starts it. It reads the public key and emoji
54
+ * ids from Discord and deploys your commands, the way a node bot does.
55
+ */
56
+ var Seedcord = class Seedcord extends PluginHost {
57
+ /** @internal */
58
+ [SeedcordBrand] = true;
59
+ /** @internal */
60
+ [HostAugmentTarget] = "@seedcord/http";
61
+ /** @internal */
62
+ [HostVersion] = version;
63
+ /** Discord REST client built from `bot.restOptions`, with both sweepers off. */
64
+ rest;
65
+ /** @see {@link IRateLimiter} */
66
+ rateLimiter;
67
+ /** @see {@link Bus} */
68
+ bus;
69
+ /**
70
+ * @deprecated Cloudflare Workers don't provide a shutdown hook.
71
+ * @throws A **SeedcordError** with `CoreLifecycleUnavailable` every time you call `addTask`.
72
+ */
73
+ shutdown = edgeShutdown;
74
+ /** Add a task that runs during startup, on the first POST. */
75
+ startup;
76
+ config;
77
+ #service;
78
+ #startup = new CoordinatedStartup();
79
+ #plugins = new PluginLifecycle(this.#startup, { addTask: () => void 0 });
80
+ #hmr = new HmrManager();
81
+ #logger = new Logger("Server", { channel: "bot" });
82
+ #readsBakedIds;
83
+ #prepared;
84
+ #started;
85
+ static #isInstantiated = false;
86
+ constructor(config) {
87
+ super("http", "edge");
88
+ if (Seedcord.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
89
+ Seedcord.#isInstantiated = true;
90
+ this.config = config;
91
+ this.startup = { addTask: this.#startup.addTask.bind(this.#startup) };
92
+ bindBotColor(() => this.config.botColor);
93
+ this.#service = new InteractionsService(this, config, edgeRestOptions(config.bot.restOptions));
94
+ this.rest = this.#service.rest;
95
+ this.rateLimiter = this.#service.rateLimiter;
96
+ this.bus = this.#service.bus;
97
+ const cli = connectDevChannel();
98
+ this.#readsBakedIds = !cli;
99
+ if (cli) {
100
+ forwardLogsToCli();
101
+ this.#startWhenAsked(cli);
102
+ }
103
+ this.#hmr.init(cli);
104
+ }
105
+ #startWhenAsked(cli) {
106
+ const tasks = new HttpStartupTasks(this, this.#service);
107
+ tasks.registerHmr(this.#hmr);
108
+ tasks.addDiscordTasks();
109
+ cli.on("seedcord:start", ({ colorLevel }) => {
110
+ setColorLevel(colorLevel);
111
+ this.#startForCli(cli);
112
+ });
113
+ }
114
+ async #startForCli(cli) {
115
+ try {
116
+ await this.#start();
117
+ } catch (error) {
118
+ cli.send("seedcord:start-failed", { error: toWireError(error) });
119
+ return;
120
+ }
121
+ cli.send("seedcord:started", { username: await usernameOf(this.rest, this.#logger) });
122
+ }
123
+ /** The bot's Discord application id. Throws if you read it before the bot starts. */
124
+ get applicationId() {
125
+ return this.#service.applicationId;
126
+ }
127
+ /**
128
+ * Answers one request to the interactions endpoint. Cloudflare passes `env` and `ctx`, but this
129
+ * method reads only `ctx`. Import your bindings as `env` from `cloudflare:workers`.
130
+ * `cf workers types` writes their types. Work that continues after the 202 response runs under `ctx.waitUntil`. Any request
131
+ * besides a POST gets a 405 before the bot starts.
132
+ */
133
+ async fetch(request, _env, ctx) {
134
+ const notPost = rejectNonPost(request);
135
+ if (notPost) return notPost;
136
+ await this.#start();
137
+ return this.#service.engine(request, ctx);
138
+ }
139
+ async #start() {
140
+ this.#prepared ??= this.#prepare();
141
+ await this.#prepared;
142
+ this.#started ??= this.#runStartup();
143
+ await this.#started;
144
+ }
145
+ async #prepare() {
146
+ Logger.configure(this.config.logger ?? {});
147
+ const ids = this.#readsBakedIds ? bakedIds() : void 0;
148
+ this.#service.authenticate();
149
+ await this.#service.subscribers.init();
150
+ await this.#service.interactions?.init();
151
+ if (!ids) return;
152
+ installLinks(ids);
153
+ installEmojis(ids.emojis);
154
+ this.#service.prepareEngine(ids.publicKey);
155
+ }
156
+ async #runStartup() {
157
+ sealAttachments(this);
158
+ this.#plugins.register(attachmentsOf(this));
159
+ try {
160
+ await this.#startup.run();
161
+ } catch (caught) {
162
+ await this.#plugins.rollback();
163
+ this.#plugins.afterTimedOutInits(() => {
164
+ this.#started = void 0;
165
+ });
166
+ throw caught;
167
+ }
168
+ }
169
+ /** @internal */
170
+ static reset() {
171
+ Seedcord.#isInstantiated = false;
172
+ }
173
+ };
174
+
175
+ //#endregion
176
+ export { ArraySource, AutocompleteHandler, ButtonHandler, ChannelMenuHandler, ComponentHandler, CursorSource, Emojis, Gated, HmrModuleHandler, InteractionHandler, InteractionMiddleware, MentionableMenuHandler, MessageContextMenuHandler, ModalFields, ModalHandler, Paginator, Plugin, RepliableHandler, ReplySender, RoleMenuHandler, Seedcord, SelectMenuHandler, SlashHandler, StringMenuHandler, Subscriber, UserContextMenuHandler, UserMenuHandler, WebhookLog, version };
177
+ //# sourceMappingURL=workerd.index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workerd.index.mjs","names":["packageVersion"],"sources":["../src/edge/defaults.ts","../src/edge/Seedcord.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { EdgeSweeperKey } from '#interfaces/Config';\nimport type { RESTOptions } from '@discordjs/rest';\nimport type { CoordinatedShutdown } from '@seedcord/core/node';\n\nexport const edgeShutdown: Pick<CoordinatedShutdown, 'addTask'> = {\n addTask: () => {\n throw new SeedcordError(SeedcordErrorCode.CoreLifecycleUnavailable);\n }\n};\n\n// @discordjs/rest skips a sweeper set to 0\nconst EDGE_SWEEPERS: Record<EdgeSweeperKey, 0> = { hashSweepInterval: 0, handlerSweepInterval: 0 };\n\nexport function edgeRestOptions(given: Partial<RESTOptions> = {}): Partial<RESTOptions> {\n for (const key of Object.keys(EDGE_SWEEPERS) as EdgeSweeperKey[]) {\n if (given[key] !== undefined) throw new SeedcordError(SeedcordErrorCode.ConfigEdgeRestSweeper, [key]);\n }\n return { ...given, ...EDGE_SWEEPERS };\n}\n","import {\n attachmentsOf,\n BAKED_IDS_KEY,\n bindBotColor,\n connectDevChannel,\n CoordinatedStartup,\n forwardLogsToCli,\n HmrManager,\n installLinks,\n PluginLifecycle,\n sealAttachments,\n toWireError\n} from '@seedcord/core/internal';\nimport { PluginHost } from '@seedcord/core/plugin';\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError, setColorLevel } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { HostAugmentTarget, HostVersion, SeedcordBrand } from '@seedcord/types/internal';\n\nimport { installEmojis } from '#src/emojis/EmojiInjector';\nimport { rejectNonPost } from '#src/engine';\nimport { HttpStartupTasks } from '#src/HttpStartupTasks';\nimport { InteractionsService } from '#src/InteractionsService';\nimport { usernameOf } from '#src/username';\nimport { version as packageVersion } from '#src/version';\n\nimport { edgeRestOptions, edgeShutdown } from './defaults';\n\nimport type { HttpEdgeConfig } from '#interfaces/Config';\nimport type { EngineContext } from '#src/engine';\nimport type { REST } from '@discordjs/rest';\nimport type { Bus } from '@seedcord/core';\nimport type { BakedIds } from '@seedcord/core/internal';\nimport type { IRateLimiter } from '@seedcord/types';\nimport type { DevChannel, SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\n// seedcord build's worker entry writes these before bot.ts loads\nfunction bakedIds(): BakedIds {\n const ids = Reflect.get(globalThis, Symbol.for(BAKED_IDS_KEY)) as BakedIds | undefined;\n if (!ids) throw new SeedcordError(SeedcordErrorCode.CoreEdgeUnbuilt);\n return ids;\n}\n\n/**\n * The HTTP-interactions bot on Cloudflare Workers. Default-export it from `bot.ts`. Cloudflare\n * calls `fetch` for every request.\n *\n * The constructor stores the config. The first POST in each isolate reads `DISCORD_BOT_TOKEN`\n * and the public key, command ids, and emoji ids that `seedcord build` wrote into the worker. It\n * loads the handler and subscriber folders, then runs the startup tasks and every plugin's `init()`\n * and `ready()`. Requests that arrive meanwhile wait for it.\n *\n * Under `seedcord dev` the bot starts when the CLI starts it. It reads the public key and emoji\n * ids from Discord and deploys your commands, the way a node bot does.\n */\nexport class Seedcord extends PluginHost<'http', 'edge'> {\n // the CLI reads these to detect and augment the instance\n /** @internal */\n public readonly [SeedcordBrand] = true;\n /** @internal */\n public readonly [HostAugmentTarget] = '@seedcord/http';\n /** @internal */\n public readonly [HostVersion]: string = packageVersion;\n\n /** Discord REST client built from `bot.restOptions`, with both sweepers off. */\n public readonly rest: REST;\n\n /** @see {@link IRateLimiter} */\n public readonly rateLimiter: IRateLimiter;\n\n /** @see {@link Bus} */\n public readonly bus: Bus;\n\n /**\n * @deprecated Cloudflare Workers don't provide a shutdown hook.\n * @throws A **SeedcordError** with `CoreLifecycleUnavailable` every time you call `addTask`.\n */\n public readonly shutdown = edgeShutdown;\n\n /** Add a task that runs during startup, on the first POST. */\n public readonly startup: Pick<CoordinatedStartup, 'addTask'>;\n\n public readonly config: HttpEdgeConfig;\n\n readonly #service: InteractionsService;\n readonly #startup = new CoordinatedStartup();\n // workerd gives a worker no shutdown hook. dispose() runs only in a rollback.\n readonly #plugins = new PluginLifecycle(this.#startup, { addTask: () => undefined });\n\n readonly #hmr = new HmrManager();\n readonly #logger = new Logger('Server', { channel: 'bot' });\n // seedcord dev reads them from Discord in its startup tasks\n readonly #readsBakedIds: boolean;\n\n #prepared?: Promise<void>;\n #started?: Promise<void> | undefined;\n\n static #isInstantiated = false;\n\n constructor(config: HttpEdgeConfig) {\n super('http', 'edge');\n if (Seedcord.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n Seedcord.#isInstantiated = true;\n\n this.config = config;\n this.startup = { addTask: this.#startup.addTask.bind(this.#startup) };\n bindBotColor(() => this.config.botColor);\n\n this.#service = new InteractionsService(this, config, edgeRestOptions(config.bot.restOptions));\n this.rest = this.#service.rest;\n this.rateLimiter = this.#service.rateLimiter;\n this.bus = this.#service.bus;\n\n const cli = connectDevChannel();\n this.#readsBakedIds = !cli;\n if (cli) {\n forwardLogsToCli();\n this.#startWhenAsked(cli);\n }\n this.#hmr.init(cli);\n }\n\n #startWhenAsked(cli: DevChannel<SeedcordFrameworkEvents, SeedcordCliEvents>): void {\n const tasks = new HttpStartupTasks(this, this.#service);\n tasks.registerHmr(this.#hmr);\n tasks.addDiscordTasks();\n cli.on('seedcord:start', ({ colorLevel }) => {\n setColorLevel(colorLevel);\n void this.#startForCli(cli);\n });\n }\n\n async #startForCli(cli: DevChannel<SeedcordFrameworkEvents, SeedcordCliEvents>): Promise<void> {\n try {\n await this.#start();\n } catch (error) {\n cli.send('seedcord:start-failed', { error: toWireError(error) });\n return;\n }\n cli.send('seedcord:started', { username: await usernameOf(this.rest, this.#logger) });\n }\n\n /** The bot's Discord application id. Throws if you read it before the bot starts. */\n public get applicationId(): string {\n return this.#service.applicationId;\n }\n\n /**\n * Answers one request to the interactions endpoint. Cloudflare passes `env` and `ctx`, but this\n * method reads only `ctx`. Import your bindings as `env` from `cloudflare:workers`.\n * `cf workers types` writes their types. Work that continues after the 202 response runs under `ctx.waitUntil`. Any request\n * besides a POST gets a 405 before the bot starts.\n */\n public async fetch(request: Request, _env?: unknown, ctx?: EngineContext): Promise<Response> {\n const notPost = rejectNonPost(request);\n if (notPost) return notPost;\n\n await this.#start();\n return this.#service.engine(request, ctx);\n }\n\n async #start(): Promise<void> {\n this.#prepared ??= this.#prepare();\n await this.#prepared;\n\n this.#started ??= this.#runStartup();\n await this.#started;\n }\n\n // once per isolate. Running the loaders again would register every subscriber twice\n async #prepare(): Promise<void> {\n // seedcord dev's codegen copy constructs this bot inside the CLI and never starts it\n Logger.configure(this.config.logger ?? {});\n const ids = this.#readsBakedIds ? bakedIds() : undefined;\n this.#service.authenticate();\n await this.#service.subscribers.init();\n await this.#service.interactions?.init();\n if (!ids) return;\n installLinks(ids);\n installEmojis(ids.emojis);\n this.#service.prepareEngine(ids.publicKey);\n }\n\n async #runStartup(): Promise<void> {\n sealAttachments(this);\n this.#plugins.register(attachmentsOf(this));\n try {\n await this.#startup.run();\n } catch (caught) {\n await this.#plugins.rollback();\n this.#plugins.afterTimedOutInits(() => {\n this.#started = undefined;\n });\n throw caught;\n }\n }\n\n /** @internal */\n protected static reset(): void {\n Seedcord.#isInstantiated = false;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@seedcord/http",
3
3
  "type": "module",
4
- "version": "0.10.2",
4
+ "version": "0.11.0",
5
5
  "description": "HTTP interactions transport for seedcord bots",
6
6
  "keywords": [
7
7
  "discord",
@@ -12,7 +12,7 @@
12
12
  "typescript"
13
13
  ],
14
14
  "engines": {
15
- "node": ">=24.11",
15
+ "node": "^24.22.1 || >=26.12.1",
16
16
  "bun": ">=1.4.2"
17
17
  },
18
18
  "repository": {
@@ -28,22 +28,14 @@
28
28
  "types": "./dist/index.d.mts",
29
29
  "exports": {
30
30
  ".": {
31
+ "workerd": {
32
+ "types": "./dist/workerd.index.d.mts",
33
+ "default": "./dist/workerd.index.mjs"
34
+ },
31
35
  "import": {
32
36
  "types": "./dist/index.d.mts",
33
37
  "default": "./dist/index.mjs"
34
38
  }
35
- },
36
- "./edge": {
37
- "import": {
38
- "types": "./dist/edge.index.d.mts",
39
- "default": "./dist/edge.index.mjs"
40
- }
41
- },
42
- "./manifest": {
43
- "import": {
44
- "types": "./dist/manifest.index.d.mts",
45
- "default": "./dist/manifest.index.mjs"
46
- }
47
39
  }
48
40
  },
49
41
  "files": [
@@ -56,33 +48,33 @@
56
48
  "@discordjs/collection": "^2.1.1",
57
49
  "@discordjs/core": "^2.6.0",
58
50
  "@discordjs/rest": "^2.6.3",
59
- "@seedcord/core": "^0.10.0",
60
- "@seedcord/custom-id": "^0.2.7",
61
- "@seedcord/errors": "^0.13.0",
62
- "@seedcord/event-emitter": "^0.1.8",
63
- "@seedcord/logger": "^0.4.4",
64
- "@seedcord/rate-limiter": "^0.1.12",
65
- "@seedcord/types": "^0.14.4",
66
- "@seedcord/utils": "^0.8.17",
51
+ "@seedcord/core": "^0.11.0",
52
+ "@seedcord/custom-id": "^0.3.0",
53
+ "@seedcord/errors": "^0.14.0",
54
+ "@seedcord/event-emitter": "^0.2.0",
55
+ "@seedcord/logger": "^0.5.0",
56
+ "@seedcord/rate-limiter": "^0.2.0",
57
+ "@seedcord/types": "^0.15.0",
58
+ "@seedcord/utils": "^0.9.0",
67
59
  "discord-api-types": "^0.38.52",
68
60
  "reflect-metadata": "^0.2.2",
69
- "type-fest": "^5.8.0"
61
+ "type-fest": "^5.9.0"
70
62
  },
71
63
  "peerDependencies": {
72
- "envapt": "^8.2.2",
64
+ "envapt": "^9.2.0",
73
65
  "typescript": "^5.9.3 || ^6.0.0 || ^7.0.0"
74
66
  },
75
67
  "devDependencies": {
76
68
  "@cloudflare/vitest-pool-workers": "^0.22.0",
77
69
  "@discordjs/builders": "^1.14.1",
78
- "@seedcord/eslint-config": "^2.5.0",
79
- "@seedcord/tsconfig": "^2.0.6",
70
+ "@seedcord/eslint-config": "^3.0.0",
71
+ "@seedcord/tsconfig": "^3.0.0",
80
72
  "@seedcord/tsdown-config": "^2.0.1",
81
73
  "@seedcord/vitest-config": "^0.0.1",
82
- "@vitest/coverage-istanbul": "^4.1.11",
83
- "envapt": "^8.2.2",
74
+ "@vitest/coverage-istanbul": "^4.1.12",
75
+ "envapt": "^9.2.0",
84
76
  "esbuild": "^0.28.1",
85
- "vitest": "^4.1.11"
77
+ "vitest": "^4.1.12"
86
78
  },
87
79
  "publishConfig": {
88
80
  "access": "public",
@@ -100,5 +92,5 @@
100
92
  "test:watch": "vitest dev",
101
93
  "coverage": "vitest run --coverage"
102
94
  },
103
- "readme": "<div align=\"center\">\n <picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://cdn.seedcord.org/assets/wordmark-dark.webp\" />\n <img src=\"https://cdn.seedcord.org/assets/wordmark-light.webp\" alt=\"seedcord\" width=\"440\" />\n </picture>\n</div>\n\n<div align=\"center\">\n <h3>The whole Discord bot, typed end to end</h3>\n <a href=\"https://seedcord.org\">Website</a> ·\n <a href=\"https://seedcord.org/guide\">Guide</a> ·\n <a href=\"https://seedcord.org/docs\">Reference</a> ·\n <a href=\"https://discord.gg/DzFxY58WXf\">Discord</a>\n</div>\n\n<br />\n\n<div align=\"center\">\n\n[![npm](https://img.shields.io/npm/v/@seedcord/http?style=flat-square&label=npm&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/@seedcord/http) [![node](https://img.shields.io/node/v/@seedcord/http?style=flat-square&label=node&labelColor=1f1f1f&color=4d7d33)](https://nodejs.org) [![bun](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fseedcord%2Fseedcord%2Fnext%2Fpackages%2Fhttp%2Fpackage.json&query=%24.engines.bun&style=flat-square&label=bun&labelColor=1f1f1f&color=fbf0df)](https://bun.com) [![license](https://img.shields.io/npm/l/@seedcord/http?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)\n\n</div>\n\n## About\n\n`@seedcord/http` runs a seedcord bot on Discord's interactions endpoint. Discord posts each interaction to your URL. The framework verifies the Ed25519 signature before dispatching it.\n\nDiscord posts only interactions over this transport. Use [`@seedcord/gateway`](https://www.npmjs.com/package/@seedcord/gateway) when the bot reacts to messages, members, or voice.\n\nUntil v1.0.0, minor versions can break.\n\n## Installation\n\n```sh\npnpm add @seedcord/http\n```\n\n`pnpm create seedcord` scaffolds the whole project. On this transport `pnpm dev` also opens a cloudflared tunnel and registers the URL with Discord.\n\n## Entry points\n\nThe root entry runs on Node through the `Seedcord` class.\n\n`@seedcord/http/edge` targets Web-standard runtimes through `createSeedcord`. The Node entry re-exports all of it. The edge build for Cloudflare Workers is a work in progress and cannot be used yet.\n"
95
+ "readme": "<div align=\"center\">\n <picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://cdn.seedcord.org/assets/wordmark-dark.webp\" />\n <img src=\"https://cdn.seedcord.org/assets/wordmark-light.webp\" alt=\"seedcord\" width=\"440\" />\n </picture>\n</div>\n\n<div align=\"center\">\n <h3>The whole Discord bot, typed end to end</h3>\n <a href=\"https://seedcord.org\">Website</a> ·\n <a href=\"https://seedcord.org/guide\">Guide</a> ·\n <a href=\"https://seedcord.org/docs\">Reference</a> ·\n <a href=\"https://seedcord.org/chat/\">Discord</a>\n</div>\n\n<br />\n\n<div align=\"center\">\n\n[![npm](https://img.shields.io/npm/v/@seedcord/http?style=flat-square&label=npm&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/@seedcord/http) [![node](https://img.shields.io/node/v/@seedcord/http?style=flat-square&label=node&labelColor=1f1f1f&color=4d7d33)](https://nodejs.org) [![bun](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fseedcord%2Fseedcord%2Fnext%2Fpackages%2Fhttp%2Fpackage.json&query=%24.engines.bun&style=flat-square&label=bun&labelColor=1f1f1f&color=fbf0df)](https://bun.com) [![license](https://img.shields.io/npm/l/@seedcord/http?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)\n\n</div>\n\n## About\n\n`@seedcord/http` is the transport for an http bot. Discord posts each interaction to your bot's URL, and the bot checks the request's Ed25519 signature before it runs your handler. It has no discord.js dependency and keeps no connection open, so it runs on your own server or on Cloudflare Workers.\n\nDiscord posts only interactions to that URL. Use [`@seedcord/gateway`](https://www.npmjs.com/package/@seedcord/gateway) when your bot reacts to messages, members, voice, or reactions.\n\nseedcord is pre-1.0, so a minor version can break your bot. Each release's changelog says what changed and how to update.\n\n## Installation\n\n`pnpm create seedcord` sets up a new bot and asks where it runs. To add seedcord to an existing project:\n\n```sh\npnpm add @seedcord/http @discordjs/builders envapt\npnpm add -D seedcord vite typescript\n```\n\n`envapt` and `typescript` are peer dependencies. `seedcord` is the CLI that runs the bot in dev and builds it. In dev, it also opens a cloudflared tunnel and sets your app's interactions URL to it.\n\n## Usage\n\n`bot.ts` creates the bot and tells it where your handlers are:\n\n```ts\nimport { Seedcord } from '@seedcord/http';\nimport { Converters, Envapter } from 'envapt';\n\nexport default new Seedcord({\n bot: {\n interactions: { path: './handlers' },\n commands: {\n path: './commands',\n guilds: Envapter.getAs('TESTING_GUILD_IDS', Converters.array(), [])\n }\n },\n subscribers: { path: null }\n});\n```\n\nRead your environment through [envapt](https://envapt.materwelon.dev), the way `TESTING_GUILD_IDS` is read above. seedcord loads your `.env` files into envapt, and binds a worker's `env` to it on Cloudflare Workers, so the same read works in dev, in a build, and on Workers. envapt also reads your shell's variables, while `process.env` misses everything in your `.env` files.\n\nHandlers read raw payloads from `discord-api-types`. Slash commands, components, checks, and plugins work the same way as on the gateway. The [guide](https://seedcord.org/guide) covers them.\n\n## Where it runs\n\n### Its own server\n\nOn Node or Bun, `Seedcord` listens on `port`, 3000 by default.\n\n### Inside your server\n\nSet `port: false`, start the bot, then pass each request to `bot.fetch()`. It takes a web `Request` and returns a `Response`, so a Hono or Next.js route can forward to it:\n\n```ts\nimport { Hono } from 'hono';\n\nimport bot from './bot';\n\nawait bot.start();\n\nconst app = new Hono();\napp.post('/interactions', (c) => bot.fetch(c.req.raw));\n```\n\n### Cloudflare Workers\n\nThe same `Seedcord` import runs on Cloudflare Workers. A `cloudflare.config.ts` beside `seedcord.config.ts` makes `seedcord build` write a worker, which you deploy with `cf deploy --prebuilt`. The [Workers guide](https://seedcord.org/guide/build/workers) covers the setup.\n"
104
96
  }