@went.tf/discord-bot-framework 2.1.0 → 2.3.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.
package/README.md CHANGED
@@ -148,6 +148,24 @@ for bots that nest a `.modal` map directly on the owning chat-input command
148
148
  synthesizes a flat `Registry<string, BotModal<Ctx>>` view so `dispatchModal`
149
149
  can consume it unchanged.
150
150
 
151
+ If other code (e.g. a locale-file type, or a helper building a command
152
+ mention) needs the literal name/id union as a *type*, derive it from the
153
+ registry you already built instead of hand-writing a parallel
154
+ `const enum CommandName { ... }` — that enum is exactly the duplication the
155
+ registry's `const` type inference exists to avoid:
156
+
157
+ ```ts
158
+ import { RegistryName } from '@went.tf/discord-bot-framework/interactions';
159
+
160
+ const chatInputCommandRegistry = createChatInputCommandRegistry([pingCommand, searchCommand]);
161
+ type ChatInputCommandName = RegistryName<typeof chatInputCommandRegistry>; // 'ping' | 'search'
162
+ ```
163
+
164
+ A command's name should only ever be written down in two places: its
165
+ `commands.json` entry and its own registry object's `name` field — nothing
166
+ else should define it again, only reference the same string (or the derived
167
+ `RegistryName` type) that those two already agree on.
168
+
151
169
  ### `@went.tf/discord-bot-framework/commands`
152
170
 
153
171
  **Every command's wire definition (name, description, options, permissions)
@@ -162,19 +180,31 @@ autocomplete?, modal? }`) no longer describe their own wire shape at all.
162
180
  below):
163
181
 
164
182
  ```json
165
- [
166
- { "type": "CHAT_INPUT", "name": "ping", "description": "Replies with pong" },
167
- {
168
- "type": "CHAT_INPUT",
169
- "name": "search",
170
- "description": "Search for something",
171
- "options": [
172
- { "type": "STRING", "name": "query", "description": "Query string", "required": true }
173
- ]
174
- }
175
- ]
183
+ {
184
+ "$schema": "./commands.schema.json",
185
+ "commands": [
186
+ { "type": "CHAT_INPUT", "name": "ping", "description": "Replies with pong" },
187
+ {
188
+ "type": "CHAT_INPUT",
189
+ "name": "search",
190
+ "description": "Search for something",
191
+ "options": [
192
+ { "type": "STRING", "name": "query", "description": "Query string", "required": true }
193
+ ]
194
+ }
195
+ ]
196
+ }
176
197
  ```
177
198
 
199
+ The `{ "$schema", "commands" }` wrapper is what makes `commands.json`
200
+ editable with real autocomplete/validation in VS Code, JetBrains, or any
201
+ other JSON-Schema-aware editor: `$schema` is only ever valid on a JSON
202
+ *object*, and a bare array can never carry one. A **bare array** (just
203
+ the `commands` value on its own, no wrapper) is still fully supported —
204
+ nothing about the wrapper is required — but you lose the inline `$schema`
205
+ editor hookup if you use it. `buildApplicationCommandsBody` accepts
206
+ either shape directly.
207
+
178
208
  `type` (and `contexts`/`integration_types`/`channel_types`) accept either
179
209
  Discord's raw numeric value or the UPPER_SNAKE_CASE string alias shown
180
210
  above — nobody should have to remember that `3` means `STRING`. Both
@@ -189,10 +219,14 @@ autocomplete?, modal? }`) no longer describe their own wire shape at all.
189
219
 
190
220
  ```ts
191
221
  import { Ajv } from 'ajv';
192
- import { parseCommandsFile, registerFrameworkSchemas } from '@went.tf/discord-bot-framework/commands/schema';
193
- import myCommandsSchema from './commands.schema.json' with { type: 'json' };
222
+ import { parseCommandsFile, registerFrameworkSchemas, resolveCommandsSchemaRefs } from '@went.tf/discord-bot-framework/commands/schema';
223
+ import myCommandsSchemaRaw from './commands.schema.json' with { type: 'json' };
194
224
  import commandsData from './commands.json' with { type: 'json' };
195
225
 
226
+ // Rewrites your schema's relative-path $refs (into node_modules) to each
227
+ // fragment's real ajv-resolvable identity - see "JSON Schema fragments" below.
228
+ const myCommandsSchema = resolveCommandsSchemaRefs(myCommandsSchemaRaw);
229
+
196
230
  const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
197
231
  registerFrameworkSchemas(ajv);
198
232
  const validate = ajv.compile(myCommandsSchema);
@@ -243,37 +277,80 @@ This package ships only the **generic, reusable JSON Schema building
243
277
  blocks** mirroring `discord-api-types`' command/option shapes — it does not
244
278
  dictate one rigid schema for your whole `commands.json` file. Compose your
245
279
  own schema on top via `$ref`/`allOf`, e.g. to narrow `name` to an enum of
246
- your bot's actual command names:
280
+ your bot's actual command names. Since a bot's own `commands.schema.json` is
281
+ authored as plain JSON (so external tools can consume it too, not just this
282
+ package's TS composition helpers), supporting **both** the bare-array and
283
+ the `{ $schema, commands }` root shapes over the same narrowed entry uses a
284
+ local `$defs` entry referenced from both branches:
247
285
 
248
286
  ```json
249
287
  {
250
- "$id": "https://schema.your-bot.example/commands-file.json",
251
- "type": "array",
252
- "items": {
253
- "oneOf": [
254
- {
255
- "allOf": [
256
- { "$ref": "https://schema.went.tf/discord-bot-framework/chat-input-command.json" },
257
- { "properties": { "name": { "enum": ["ping", "search"] } } }
258
- ]
288
+ "$defs": {
289
+ "entry": {
290
+ "oneOf": [
291
+ {
292
+ "allOf": [
293
+ { "$ref": "../node_modules/@went.tf/discord-bot-framework/build/commands/schema/chat-input-command.json" },
294
+ { "properties": { "name": { "enum": ["ping", "search"] } } }
295
+ ]
296
+ },
297
+ { "$ref": "../node_modules/@went.tf/discord-bot-framework/build/commands/schema/context-menu-command.json" }
298
+ ]
299
+ }
300
+ },
301
+ "oneOf": [
302
+ { "type": "array", "items": { "$ref": "#/$defs/entry" } },
303
+ {
304
+ "type": "object",
305
+ "properties": {
306
+ "$schema": { "type": "string" },
307
+ "commands": { "type": "array", "items": { "$ref": "#/$defs/entry" } }
259
308
  },
260
- { "$ref": "https://schema.went.tf/discord-bot-framework/context-menu-command.json" }
261
- ]
262
- }
309
+ "required": ["commands"],
310
+ "additionalProperties": false
311
+ }
312
+ ]
263
313
  }
264
314
  ```
265
315
 
266
- Call `registerFrameworkSchemas(ajv)` before compiling your own schema so its
267
- `$ref`s resolve. The fragments this package ships (all under
316
+ `$ref` uses a **real relative filesystem path** into `node_modules` (the
317
+ exact `../` count doesn't have to be exactly right — see below) rather than
318
+ an opaque URL — this is what makes an editor (VS Code, JetBrains, ...)
319
+ actually able to follow it and offer real autocomplete/validation while you
320
+ hand-edit `commands.schema.json`, since it points at a real file: this
321
+ package ships its raw fragment `.json` mirrors precisely so a path like this
322
+ resolves to something real on disk, no network access involved.
323
+
324
+ **ajv** can't follow that same relative path directly — it resolves relative
325
+ `$ref`s via real RFC3986 URI resolution against the referencing schema's own
326
+ `$id`, and `node_modules` is virtualized under a symlinked store by pnpm (and
327
+ similar tools), so there is no `$id` value that makes ajv's own resolution
328
+ land on the right file for every install. `resolveCommandsSchemaRefs()`
329
+ sidesteps this: it rewrites any `$ref` matching a shipped fragment's
330
+ filename to that fragment's canonical `$id` (whatever relative-path prefix
331
+ you used), which `registerFrameworkSchemas(ajv)` has already registered.
332
+ Your own local refs (e.g. `#/$defs/...`) are left untouched:
333
+
334
+ ```ts
335
+ import myCommandsSchemaRaw from './commands.schema.json' with { type: 'json' };
336
+ const myCommandsSchema = resolveCommandsSchemaRefs(myCommandsSchemaRaw);
337
+ registerFrameworkSchemas(ajv); // before ajv.compile(myCommandsSchema)
338
+ ```
339
+
340
+ If you don't need the name-narrowing (or any other bot-specific constraint),
341
+ skip authoring your own schema entirely and validate directly against this
342
+ package's own `commandsFileSchema` — it already accepts both root shapes.
343
+
344
+ The fragments this package ships (all under
268
345
  `@went.tf/discord-bot-framework/commands/schema`, and as real standalone
269
346
  `.json` files under `build/commands/schema/` for non-TS tooling):
270
- `commands-file`, `chat-input-command`, `context-menu-command`,
271
- `application-command-option` (and its `application-command-leaf-option`/
272
- `application-command-subcommand`/`application-command-subcommand-group`
273
- building blocks), `application-command-option-choice`,
274
- `default-member-permissions`, `option-name`, `context-menu-name`,
275
- `application-command-type`, `interaction-context-type`,
276
- `application-integration-type`, `channel-type`.
347
+ `commands-file`, `command-file-entry`, `chat-input-command`,
348
+ `context-menu-command`, `application-command-option` (and its
349
+ `application-command-leaf-option`/`application-command-subcommand`/
350
+ `application-command-subcommand-group` building blocks),
351
+ `application-command-option-choice`, `default-member-permissions`,
352
+ `option-name`, `context-menu-name`, `application-command-type`,
353
+ `interaction-context-type`, `application-integration-type`, `channel-type`.
277
354
 
278
355
  `./commands/schema` also exports `enum-maps.ts`'s `APPLICATION_COMMAND_TYPE_MAP`,
279
356
  `APPLICATION_COMMAND_OPTION_TYPE_MAP`, `INTERACTION_CONTEXT_TYPE_MAP`,