@nectar-js/nectar 0.1.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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/cli-Ce-ZUj6M.js +2225 -0
- package/dist/cli-Ce-ZUj6M.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/index-DGMxBsub.d.ts +757 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins-CGvM19v9.js +663 -0
- package/dist/plugins-CGvM19v9.js.map +1 -0
- package/dist/registration-CaE0QBT6.js +545 -0
- package/dist/registration-CaE0QBT6.js.map +1 -0
- package/dist/runtime-CZJeZvSL.js +891 -0
- package/dist/runtime-CZJeZvSL.js.map +1 -0
- package/dist/start.d.ts +8 -0
- package/dist/start.js +15 -0
- package/dist/start.js.map +1 -0
- package/dist/testing.d.ts +111 -0
- package/dist/testing.js +320 -0
- package/dist/testing.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
import { ButtonInteraction, ChannelSelectMenuInteraction, ChatInputCommandInteraction, Client, ClientEvents, ClientOptions, GatewayIntentBits, Interaction, MentionableSelectMenuInteraction, MessageContextMenuCommandInteraction, ModalSubmitInteraction, PermissionResolvable, RoleSelectMenuInteraction, StringSelectMenuInteraction, UserContextMenuCommandInteraction, UserSelectMenuInteraction } from "discord.js";
|
|
2
|
+
import { ApplicationCommandType, ApplicationIntegrationType, ChannelType, InteractionContextType, LocalizationMap, RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10";
|
|
3
|
+
//#region src/version.d.ts
|
|
4
|
+
/** From package.json, which sits one level up from both `src/` and `dist/`. */
|
|
5
|
+
declare const version: string;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/commands/meta.d.ts
|
|
8
|
+
type CommandType = "chatInput" | "user" | "message";
|
|
9
|
+
type OptionType = "string" | "integer" | "number" | "boolean" | "user" | "channel" | "role" | "mentionable" | "attachment";
|
|
10
|
+
interface OptionChoice<Value extends string | number = string | number> {
|
|
11
|
+
name: string;
|
|
12
|
+
value: Value;
|
|
13
|
+
nameLocalizations?: LocalizationMap;
|
|
14
|
+
}
|
|
15
|
+
interface BaseOption {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
nameLocalizations?: LocalizationMap;
|
|
20
|
+
descriptionLocalizations?: LocalizationMap;
|
|
21
|
+
}
|
|
22
|
+
interface StringOption extends BaseOption {
|
|
23
|
+
type: "string";
|
|
24
|
+
choices?: OptionChoice<string>[];
|
|
25
|
+
autocomplete?: boolean;
|
|
26
|
+
minLength?: number;
|
|
27
|
+
maxLength?: number;
|
|
28
|
+
}
|
|
29
|
+
interface IntegerOption extends BaseOption {
|
|
30
|
+
type: "integer";
|
|
31
|
+
choices?: OptionChoice<number>[];
|
|
32
|
+
autocomplete?: boolean;
|
|
33
|
+
minValue?: number;
|
|
34
|
+
maxValue?: number;
|
|
35
|
+
}
|
|
36
|
+
interface NumberOption extends BaseOption {
|
|
37
|
+
type: "number";
|
|
38
|
+
choices?: OptionChoice<number>[];
|
|
39
|
+
autocomplete?: boolean;
|
|
40
|
+
minValue?: number;
|
|
41
|
+
maxValue?: number;
|
|
42
|
+
}
|
|
43
|
+
interface ChannelOption extends BaseOption {
|
|
44
|
+
type: "channel";
|
|
45
|
+
channelTypes?: ChannelType[];
|
|
46
|
+
}
|
|
47
|
+
interface SimpleOption extends BaseOption {
|
|
48
|
+
type: "boolean" | "user" | "role" | "mentionable" | "attachment";
|
|
49
|
+
}
|
|
50
|
+
type CommandOption = StringOption | IntegerOption | NumberOption | ChannelOption | SimpleOption;
|
|
51
|
+
/**
|
|
52
|
+
* Registration settings that Discord only accepts on a top-level command.
|
|
53
|
+
* For a command with subcommands these live in the parent directory's `route.ts`.
|
|
54
|
+
*/
|
|
55
|
+
interface TopLevelMeta {
|
|
56
|
+
defaultMemberPermissions?: bigint | string | number | null;
|
|
57
|
+
nsfw?: boolean;
|
|
58
|
+
contexts?: InteractionContextType[];
|
|
59
|
+
integrationTypes?: ApplicationIntegrationType[];
|
|
60
|
+
}
|
|
61
|
+
/** `export const meta` in a `command.ts`. */
|
|
62
|
+
interface CommandMeta extends TopLevelMeta {
|
|
63
|
+
/** Overrides the name derived from the directory. */
|
|
64
|
+
name?: string;
|
|
65
|
+
/** Required for chat input commands and subcommands. Must be omitted for context menus. */
|
|
66
|
+
description?: string;
|
|
67
|
+
/** Defaults to `"chatInput"`. Context menu commands cannot be nested or have options. */
|
|
68
|
+
type?: CommandType;
|
|
69
|
+
options?: CommandOption[];
|
|
70
|
+
nameLocalizations?: LocalizationMap;
|
|
71
|
+
descriptionLocalizations?: LocalizationMap;
|
|
72
|
+
}
|
|
73
|
+
/** `export const meta` in a `route.ts` under `commands/`. Describes a parent command or subcommand group. */
|
|
74
|
+
interface CommandRouteMeta extends TopLevelMeta {
|
|
75
|
+
name?: string;
|
|
76
|
+
description: string;
|
|
77
|
+
nameLocalizations?: LocalizationMap;
|
|
78
|
+
descriptionLocalizations?: LocalizationMap;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/compiler/diagnostics.d.ts
|
|
82
|
+
type Severity = "error" | "warning";
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/compiler/routes.d.ts
|
|
85
|
+
type RouteCategory = "command" | "component" | "event";
|
|
86
|
+
type RouteKind = "command" | "autocomplete" | "button" | "select" | "modal" | "event";
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/components/compile.d.ts
|
|
89
|
+
type SelectKind = "string" | "user" | "role" | "channel" | "mentionable";
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/components/customId.d.ts
|
|
92
|
+
/** Discord rejects custom IDs longer than this. */
|
|
93
|
+
declare const MAX_CUSTOM_ID_LENGTH = 100;
|
|
94
|
+
declare class CustomIdTooLongError extends Error {
|
|
95
|
+
readonly customId: string;
|
|
96
|
+
readonly routeId: string;
|
|
97
|
+
constructor(customId: string, routeId: string);
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/components/params.d.ts
|
|
101
|
+
/**
|
|
102
|
+
* A Standard Schema (https://standardschema.dev) validator, which zod, valibot, and arktype
|
|
103
|
+
* all produce. Only the result's `issues` are looked at: a schema that transforms the value
|
|
104
|
+
* does not change what the handler receives.
|
|
105
|
+
*/
|
|
106
|
+
interface StandardSchemaLike<V = unknown> {
|
|
107
|
+
"~standard": {
|
|
108
|
+
validate(value: V): {
|
|
109
|
+
issues?: ReadonlyArray<unknown> | undefined;
|
|
110
|
+
} | Promise<{
|
|
111
|
+
issues?: ReadonlyArray<unknown> | undefined;
|
|
112
|
+
}>;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Checks one custom ID parameter. A function passes by returning anything but `false` and
|
|
117
|
+
* fails by returning `false` or throwing. A schema fails by reporting issues.
|
|
118
|
+
*/
|
|
119
|
+
type ParamValidator<V = string | string[]> = ((value: V) => unknown) | StandardSchemaLike<V>;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/runtime/logger.d.ts
|
|
122
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
123
|
+
/** Structured metadata attached to a log line. `error` is the thrown value, when there is one. */
|
|
124
|
+
type LogFields = Record<string, unknown>;
|
|
125
|
+
interface LogRecord {
|
|
126
|
+
level: LogLevel;
|
|
127
|
+
message: string;
|
|
128
|
+
/** Epoch milliseconds. */
|
|
129
|
+
at: number;
|
|
130
|
+
fields: LogFields;
|
|
131
|
+
}
|
|
132
|
+
/** Where log records go. The default prints to the console; an app can hand records to any library. */
|
|
133
|
+
type LogSink = (record: LogRecord) => void;
|
|
134
|
+
interface LoggerOptions {
|
|
135
|
+
/** Lowest level that reaches the sink. Defaults to `info`. */
|
|
136
|
+
level?: LogLevel;
|
|
137
|
+
sink?: LogSink;
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/runtime/signals.d.ts
|
|
141
|
+
/** Structured, non-sensitive description of an interaction for logs and signals. */
|
|
142
|
+
interface InteractionMeta {
|
|
143
|
+
type: "chatInput" | "userContextMenu" | "messageContextMenu" | "autocomplete" | "button" | "select" | "modal" | "unknown";
|
|
144
|
+
/** Full command name with subcommand group and subcommand, for commands and autocomplete. */
|
|
145
|
+
command?: string;
|
|
146
|
+
/** Custom ID with Nectar param values replaced by `*`. */
|
|
147
|
+
customId?: string;
|
|
148
|
+
guildId: string | null;
|
|
149
|
+
channelId: string | null;
|
|
150
|
+
userId: string | null;
|
|
151
|
+
}
|
|
152
|
+
interface RegistrationScopeResult {
|
|
153
|
+
scope: string;
|
|
154
|
+
/** A bulk overwrite was sent. */
|
|
155
|
+
applied: boolean;
|
|
156
|
+
}
|
|
157
|
+
/** Everything the runtime reports about itself, without the timestamp. */
|
|
158
|
+
type SignalData = {
|
|
159
|
+
type: "interaction:start";
|
|
160
|
+
trace: string;
|
|
161
|
+
interaction: InteractionMeta;
|
|
162
|
+
} | {
|
|
163
|
+
type: "route:match";
|
|
164
|
+
trace: string;
|
|
165
|
+
interaction: InteractionMeta;
|
|
166
|
+
route: RouteInfo;
|
|
167
|
+
} | {
|
|
168
|
+
type: "middleware:enter";
|
|
169
|
+
trace: string;
|
|
170
|
+
interaction: InteractionMeta;
|
|
171
|
+
route: RouteInfo;
|
|
172
|
+
file: string;
|
|
173
|
+
} | {
|
|
174
|
+
type: "handler:enter";
|
|
175
|
+
trace: string;
|
|
176
|
+
interaction: InteractionMeta;
|
|
177
|
+
route: RouteInfo;
|
|
178
|
+
} | {
|
|
179
|
+
type: "handler:complete";
|
|
180
|
+
trace: string;
|
|
181
|
+
interaction: InteractionMeta;
|
|
182
|
+
route: RouteInfo;
|
|
183
|
+
/** Milliseconds the handler took. */
|
|
184
|
+
duration: number;
|
|
185
|
+
} | {
|
|
186
|
+
type: "interaction:complete";
|
|
187
|
+
trace: string;
|
|
188
|
+
interaction: InteractionMeta;
|
|
189
|
+
route: RouteInfo;
|
|
190
|
+
/** Milliseconds since the runtime received the interaction. */
|
|
191
|
+
duration: number;
|
|
192
|
+
/** `false` when a middleware stopped the chain before the handler. */
|
|
193
|
+
handled: boolean;
|
|
194
|
+
} | {
|
|
195
|
+
type: "interaction:fail";
|
|
196
|
+
trace: string;
|
|
197
|
+
interaction: InteractionMeta;
|
|
198
|
+
route: RouteInfo;
|
|
199
|
+
error: unknown;
|
|
200
|
+
/** The `error.ts` that handled it, or `null` for the default boundary. */
|
|
201
|
+
boundary: string | null;
|
|
202
|
+
} | {
|
|
203
|
+
/** The interaction was refused before any application code ran. */
|
|
204
|
+
type: "interaction:reject";
|
|
205
|
+
trace: string;
|
|
206
|
+
interaction: InteractionMeta;
|
|
207
|
+
reason: RejectReason;
|
|
208
|
+
/** Known when the route matched but a parameter failed validation. */
|
|
209
|
+
route?: RouteInfo;
|
|
210
|
+
/** The parameter that failed, for `invalid-param`. Its value is never included. */
|
|
211
|
+
param?: string;
|
|
212
|
+
} | {
|
|
213
|
+
type: "event:fail";
|
|
214
|
+
event: string;
|
|
215
|
+
route: RouteInfo;
|
|
216
|
+
error: unknown;
|
|
217
|
+
boundary: string | null;
|
|
218
|
+
} | {
|
|
219
|
+
type: "registration:start";
|
|
220
|
+
scopes: string[];
|
|
221
|
+
} | {
|
|
222
|
+
type: "registration:complete";
|
|
223
|
+
scopes: RegistrationScopeResult[];
|
|
224
|
+
duration: number;
|
|
225
|
+
} | {
|
|
226
|
+
type: "gateway:connect";
|
|
227
|
+
shard: number;
|
|
228
|
+
resumed: boolean;
|
|
229
|
+
} | {
|
|
230
|
+
type: "gateway:disconnect";
|
|
231
|
+
shard: number;
|
|
232
|
+
code: number;
|
|
233
|
+
} | {
|
|
234
|
+
type: "shutdown";
|
|
235
|
+
};
|
|
236
|
+
type RejectReason =
|
|
237
|
+
/** A command, autocomplete option, or context menu Discord sent that no route serves. */
|
|
238
|
+
"no-route" |
|
|
239
|
+
/** An interaction type Nectar does not route at all. */
|
|
240
|
+
"unknown-interaction" |
|
|
241
|
+
/** A Nectar custom ID that cannot be decoded. */
|
|
242
|
+
"malformed" |
|
|
243
|
+
/** A decoded custom ID whose short ID names no route of that kind. */
|
|
244
|
+
"unknown-route" |
|
|
245
|
+
/** The wrong number of values for the route. */
|
|
246
|
+
"param-count" |
|
|
247
|
+
/** A route's own validator refused a value. */
|
|
248
|
+
"invalid-param";
|
|
249
|
+
type SignalType = SignalData["type"];
|
|
250
|
+
type Signal = SignalData & {
|
|
251
|
+
/** Epoch milliseconds. */
|
|
252
|
+
at: number;
|
|
253
|
+
};
|
|
254
|
+
type SignalListener = (signal: Signal) => void;
|
|
255
|
+
interface SignalEmitter {
|
|
256
|
+
/** Delivers every signal to `listener`. Returns a function that unsubscribes. */
|
|
257
|
+
on(listener: SignalListener): () => void;
|
|
258
|
+
emit(data: SignalData): void;
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/runtime/types.d.ts
|
|
262
|
+
type Env = "development" | "test" | "production";
|
|
263
|
+
interface RouteInfo {
|
|
264
|
+
id: string;
|
|
265
|
+
category: RouteCategory;
|
|
266
|
+
path: string;
|
|
267
|
+
/** Absolute path of the handler file. */
|
|
268
|
+
file: string;
|
|
269
|
+
}
|
|
270
|
+
interface Trace {
|
|
271
|
+
/** The interaction ID. */
|
|
272
|
+
id: string;
|
|
273
|
+
/** When the runtime received the interaction, epoch milliseconds. */
|
|
274
|
+
receivedAt: number;
|
|
275
|
+
/** Milliseconds since Discord created the interaction. Replies must land within 3000. */
|
|
276
|
+
elapsed(): number;
|
|
277
|
+
}
|
|
278
|
+
type Params = Record<string, string | string[]>;
|
|
279
|
+
interface InteractionContext<I = Interaction, P = Params> {
|
|
280
|
+
interaction: I;
|
|
281
|
+
client: Client;
|
|
282
|
+
route: RouteInfo;
|
|
283
|
+
params: P;
|
|
284
|
+
env: Env;
|
|
285
|
+
trace: Trace;
|
|
286
|
+
/** What plugins provide. Empty without plugins. */
|
|
287
|
+
services: NectarServices;
|
|
288
|
+
}
|
|
289
|
+
interface EventContext {
|
|
290
|
+
client: Client;
|
|
291
|
+
route: RouteInfo;
|
|
292
|
+
env: Env;
|
|
293
|
+
services: NectarServices;
|
|
294
|
+
}
|
|
295
|
+
/** What a middleware's `next()` accepts: extra fields merged into the downstream context. */
|
|
296
|
+
type ContextExtension = Record<string, unknown>;
|
|
297
|
+
declare const extension: unique symbol;
|
|
298
|
+
/** Carries the extension type through `next()`'s return value so `defineMiddleware` can infer it. */
|
|
299
|
+
interface Extended<E extends ContextExtension> {
|
|
300
|
+
readonly [extension]?: E;
|
|
301
|
+
}
|
|
302
|
+
type Next = <E extends ContextExtension = Record<never, never>>(extension?: E) => Promise<Extended<E>>;
|
|
303
|
+
type Middleware<E extends ContextExtension = ContextExtension> = ((ctx: InteractionContext, next: Next) => unknown) & Extended<E>;
|
|
304
|
+
/** The context fields a middleware module adds downstream. `{}` for plain functions. */
|
|
305
|
+
type MiddlewareExtension<M> = M extends {
|
|
306
|
+
default: Extended<infer E>;
|
|
307
|
+
} ? ContextExtension extends E ? Record<never, never> : E : Record<never, never>;
|
|
308
|
+
/**
|
|
309
|
+
* Return `"unhandled"` or throw to pass the error to the next boundary up. Returning anything
|
|
310
|
+
* else marks it handled.
|
|
311
|
+
*/
|
|
312
|
+
type ErrorHandler = (error: unknown, ctx: InteractionContext | EventContext) => unknown | Promise<unknown>;
|
|
313
|
+
/**
|
|
314
|
+
* Framework logger. `fields` is structured metadata for the sink: route identity, trace, guild,
|
|
315
|
+
* user, and so on. The thrown value goes under `error`.
|
|
316
|
+
*/
|
|
317
|
+
interface Logger {
|
|
318
|
+
debug(message: string, fields?: LogFields): void;
|
|
319
|
+
info(message: string, fields?: LogFields): void;
|
|
320
|
+
warn(message: string, fields?: LogFields): void;
|
|
321
|
+
error(message: string, fields?: LogFields): void;
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/cli/project.d.ts
|
|
325
|
+
interface Project {
|
|
326
|
+
/** Directory holding the config file. Every relative config path resolves against it. */
|
|
327
|
+
root: string;
|
|
328
|
+
configFile: string;
|
|
329
|
+
/** With the overrides for `env` applied. */
|
|
330
|
+
config: NectarConfig;
|
|
331
|
+
appDir: string;
|
|
332
|
+
outDir: string;
|
|
333
|
+
env: Env;
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/events/compile.d.ts
|
|
337
|
+
/** `export const meta` in an `event.ts`. Every field is optional. */
|
|
338
|
+
interface EventMeta {
|
|
339
|
+
/** Remove the listener after the first call. */
|
|
340
|
+
once?: boolean;
|
|
341
|
+
/** Handlers of the same event run in ascending order. Ties break on route identity. Defaults to 0. */
|
|
342
|
+
order?: number;
|
|
343
|
+
/**
|
|
344
|
+
* How the handlers of this event run relative to each other. Every handler that sets it
|
|
345
|
+
* must agree. Defaults to `"sequential"`.
|
|
346
|
+
*/
|
|
347
|
+
mode?: EventMode;
|
|
348
|
+
}
|
|
349
|
+
type EventMode = "sequential" | "concurrent";
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/manifest/schema.d.ts
|
|
352
|
+
declare const MANIFEST_VERSION = 1;
|
|
353
|
+
/**
|
|
354
|
+
* The compiled app, as written to `.nectar/manifest.json`.
|
|
355
|
+
*
|
|
356
|
+
* Every file path is relative to `appDir`, with `/` separators, so a manifest built on one
|
|
357
|
+
* machine loads on another. `appDir` itself is relative to the manifest's own directory.
|
|
358
|
+
*/
|
|
359
|
+
interface Manifest {
|
|
360
|
+
version: typeof MANIFEST_VERSION;
|
|
361
|
+
/** `@nectar-js/nectar` version that produced this manifest. */
|
|
362
|
+
nectar: string;
|
|
363
|
+
appDir: string;
|
|
364
|
+
routes: ManifestRoute[];
|
|
365
|
+
commands: ManifestCommand[];
|
|
366
|
+
events: ManifestEvent[];
|
|
367
|
+
}
|
|
368
|
+
interface ManifestRouteBase {
|
|
369
|
+
/** Canonical identity, `<category>:<path>`. Unique together with `kind`. */
|
|
370
|
+
id: string;
|
|
371
|
+
category: RouteCategory;
|
|
372
|
+
path: string;
|
|
373
|
+
file: string;
|
|
374
|
+
/** Middleware files in execution order, root first. */
|
|
375
|
+
middleware: string[];
|
|
376
|
+
/** Error boundary files, nearest first. */
|
|
377
|
+
errors: string[];
|
|
378
|
+
/** Plugins that changed this route's chains, in config order. */
|
|
379
|
+
plugins: string[];
|
|
380
|
+
}
|
|
381
|
+
interface ManifestCommandRoute extends ManifestRouteBase {
|
|
382
|
+
kind: "command";
|
|
383
|
+
}
|
|
384
|
+
interface ManifestAutocompleteRoute extends ManifestRouteBase {
|
|
385
|
+
kind: "autocomplete";
|
|
386
|
+
/** Option names handled, each a named export of the file. */
|
|
387
|
+
options: string[];
|
|
388
|
+
}
|
|
389
|
+
interface ManifestComponentRoute extends ManifestRouteBase {
|
|
390
|
+
kind: "button" | "select" | "modal";
|
|
391
|
+
shortId: string;
|
|
392
|
+
params: string[];
|
|
393
|
+
catchAll: string | null;
|
|
394
|
+
selectKind: SelectKind | null;
|
|
395
|
+
overhead: number;
|
|
396
|
+
}
|
|
397
|
+
interface ManifestEventRoute extends ManifestRouteBase {
|
|
398
|
+
kind: "event";
|
|
399
|
+
event: string;
|
|
400
|
+
once: boolean;
|
|
401
|
+
order: number;
|
|
402
|
+
}
|
|
403
|
+
type ManifestRoute = ManifestCommandRoute | ManifestAutocompleteRoute | ManifestComponentRoute | ManifestEventRoute;
|
|
404
|
+
interface ManifestCommand {
|
|
405
|
+
name: string;
|
|
406
|
+
type: number;
|
|
407
|
+
payload: RESTPostAPIApplicationCommandsJSONBody;
|
|
408
|
+
/** Handler position (`""`, `"sub"`, or `"group/sub"`) to command route ID. */
|
|
409
|
+
handlers: Record<string, string>;
|
|
410
|
+
}
|
|
411
|
+
interface ManifestEvent {
|
|
412
|
+
name: string;
|
|
413
|
+
mode: EventMode;
|
|
414
|
+
/** Event route IDs in execution order. */
|
|
415
|
+
handlers: string[];
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region src/plugins/index.d.ts
|
|
419
|
+
/**
|
|
420
|
+
* A plugin takes part in compilation and the runtime lifecycle. A library that only exports
|
|
421
|
+
* functions for handlers to call does not need to be one.
|
|
422
|
+
*/
|
|
423
|
+
interface NectarPlugin {
|
|
424
|
+
/** Unique among the configured plugins. Named in diagnostics and in the manifest. */
|
|
425
|
+
name: string;
|
|
426
|
+
version?: string;
|
|
427
|
+
/**
|
|
428
|
+
* Runs after the route graph is validated and before the manifest is written. The graph is
|
|
429
|
+
* frozen; return changes and the compiler applies and checks them. Plugins run in config
|
|
430
|
+
* order, each seeing the changes of the ones before it.
|
|
431
|
+
*/
|
|
432
|
+
transform?(graph: PluginGraph): Maybe<PluginChange[]> | Promise<Maybe<PluginChange[]>>;
|
|
433
|
+
/** Declarations appended to `.nectar/types.d.ts`. */
|
|
434
|
+
types?(graph: PluginGraph): Maybe<string>;
|
|
435
|
+
/** Extra `nectar <name>` commands. */
|
|
436
|
+
commands?: PluginCommand[];
|
|
437
|
+
/**
|
|
438
|
+
* Runs when the runtime starts, before any handler is imported and before login. A sharded
|
|
439
|
+
* bot runs it in every process. Returned services land on `ctx.services` for every handler
|
|
440
|
+
* and middleware.
|
|
441
|
+
*/
|
|
442
|
+
start?(app: PluginApp): Maybe<Partial<NectarServices>> | Promise<Maybe<Partial<NectarServices>>>;
|
|
443
|
+
/** Runs on shutdown, after in-flight interactions drain and before the client is destroyed. */
|
|
444
|
+
stop?(app: PluginApp): void | Promise<void>;
|
|
445
|
+
/**
|
|
446
|
+
* Runs once per application, in the process that runs shard 0, after every `start`. For work
|
|
447
|
+
* that must not repeat per shard process: a scheduled job, a web server, posting stats.
|
|
448
|
+
*/
|
|
449
|
+
startGlobal?(app: PluginApp): void | Promise<void>;
|
|
450
|
+
/** Runs on shutdown in the process that ran `startGlobal`, before any `stop`. */
|
|
451
|
+
stopGlobal?(app: PluginApp): void | Promise<void>;
|
|
452
|
+
}
|
|
453
|
+
/** A hook may return nothing, so a body without `return` type-checks. */
|
|
454
|
+
type Maybe<T> = T | undefined | void;
|
|
455
|
+
type PluginChange = {
|
|
456
|
+
type: "middleware";
|
|
457
|
+
/** Route ID, `<category>:<path>`. */
|
|
458
|
+
route: string;
|
|
459
|
+
/**
|
|
460
|
+
* Only the route of this kind. A command and its autocomplete share an ID; without
|
|
461
|
+
* `kind`, both get the middleware, as they would from a `middleware.ts`.
|
|
462
|
+
*/
|
|
463
|
+
kind?: RouteKind;
|
|
464
|
+
/** Absolute path of a module whose default export is a middleware. */
|
|
465
|
+
file: string;
|
|
466
|
+
/** `outer` (default) runs before the app's own middleware, `inner` right before the handler. */
|
|
467
|
+
position?: "outer" | "inner";
|
|
468
|
+
} | {
|
|
469
|
+
type: "diagnostic";
|
|
470
|
+
severity: Severity;
|
|
471
|
+
code: string;
|
|
472
|
+
message: string;
|
|
473
|
+
file?: string;
|
|
474
|
+
route?: string;
|
|
475
|
+
};
|
|
476
|
+
type DeepReadonly<T> = T extends (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]>; } : T;
|
|
477
|
+
/** The compiled app as a plugin sees it: the manifest shape with absolute file paths, frozen. */
|
|
478
|
+
interface PluginGraph {
|
|
479
|
+
readonly appDir: string;
|
|
480
|
+
readonly routes: DeepReadonly<ManifestRoute[]>;
|
|
481
|
+
readonly commands: DeepReadonly<ManifestCommand[]>;
|
|
482
|
+
readonly events: DeepReadonly<ManifestEvent[]>;
|
|
483
|
+
}
|
|
484
|
+
interface PluginApp {
|
|
485
|
+
readonly client: Client;
|
|
486
|
+
readonly env: Env;
|
|
487
|
+
readonly logger: Logger;
|
|
488
|
+
readonly signals: SignalEmitter;
|
|
489
|
+
readonly manifest: Manifest;
|
|
490
|
+
}
|
|
491
|
+
interface PluginCommand {
|
|
492
|
+
name: string;
|
|
493
|
+
description: string;
|
|
494
|
+
options?: Record<string, {
|
|
495
|
+
type: "boolean" | "string";
|
|
496
|
+
description: string;
|
|
497
|
+
}>;
|
|
498
|
+
/** Returns the exit code. */
|
|
499
|
+
run(ctx: PluginCommandContext): number | Promise<number>;
|
|
500
|
+
}
|
|
501
|
+
interface PluginCommandContext {
|
|
502
|
+
project: Project;
|
|
503
|
+
flags: Record<string, string | boolean | undefined>;
|
|
504
|
+
out(line: string): void;
|
|
505
|
+
err(line: string): void;
|
|
506
|
+
}
|
|
507
|
+
declare function definePlugin(plugin: NectarPlugin): NectarPlugin;
|
|
508
|
+
/** A plugin misbehaved: threw from a hook, or provided something that clashes. */
|
|
509
|
+
declare class PluginError extends Error {
|
|
510
|
+
readonly plugin: string;
|
|
511
|
+
readonly detail: string;
|
|
512
|
+
constructor(plugin: string, detail: string);
|
|
513
|
+
}
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/config.d.ts
|
|
516
|
+
/** `nectar.config.ts`: `export default defineConfig({ ... })`. */
|
|
517
|
+
interface NectarConfig {
|
|
518
|
+
/**
|
|
519
|
+
* Bot token. Usually `process.env.DISCORD_TOKEN`; the CLI falls back to that variable when
|
|
520
|
+
* this is omitted or empty.
|
|
521
|
+
*/
|
|
522
|
+
token?: string | undefined;
|
|
523
|
+
/** Application ID, for command registration. Falls back to `DISCORD_APPLICATION_ID`. */
|
|
524
|
+
applicationId?: string | undefined;
|
|
525
|
+
intents: ClientOptions["intents"];
|
|
526
|
+
partials?: ClientOptions["partials"];
|
|
527
|
+
/** Extra discord.js client options. `intents` and `partials` above take precedence. */
|
|
528
|
+
client?: Partial<ClientOptions>;
|
|
529
|
+
/** Import every handler at startup. Defaults to `true` in production, `false` otherwise. */
|
|
530
|
+
eager?: boolean;
|
|
531
|
+
/** Overrides `NODE_ENV`. */
|
|
532
|
+
env?: Env;
|
|
533
|
+
/**
|
|
534
|
+
* Framework log level and sink. The default sink prints to the console; pass `sink` to hand
|
|
535
|
+
* records to your own logger. Handlers are free to log however they like.
|
|
536
|
+
*/
|
|
537
|
+
logger?: LoggerOptions;
|
|
538
|
+
/** Called with every framework signal: interaction lifecycle, failures, gateway state, shutdown. */
|
|
539
|
+
observe?: (signal: Signal) => void;
|
|
540
|
+
/** Plugins, in the order their hooks run. This is the only way to register one. */
|
|
541
|
+
plugins?: NectarPlugin[];
|
|
542
|
+
/** Route directory, relative to the project root. Defaults to `app`. */
|
|
543
|
+
appDir?: string;
|
|
544
|
+
/** Build output, relative to the project root. Defaults to `.nectar`. */
|
|
545
|
+
outDir?: string;
|
|
546
|
+
dev?: {
|
|
547
|
+
/** Guilds that receive commands instantly while developing. */
|
|
548
|
+
guilds?: string[];
|
|
549
|
+
};
|
|
550
|
+
commands?: {
|
|
551
|
+
/**
|
|
552
|
+
* Where commands are registered outside development: everywhere, or only in the listed
|
|
553
|
+
* guilds. Defaults to `"global"`. Development always uses `dev.guilds`.
|
|
554
|
+
*/
|
|
555
|
+
target?: "global" | string[];
|
|
556
|
+
};
|
|
557
|
+
/**
|
|
558
|
+
* Overrides for one environment, applied once the environment is known. Each key replaces
|
|
559
|
+
* the value above it; nested objects are not merged.
|
|
560
|
+
*/
|
|
561
|
+
environments?: Partial<Record<Env, Partial<Omit<NectarConfig, "env" | "environments">>>>;
|
|
562
|
+
}
|
|
563
|
+
declare function defineConfig(config: NectarConfig): NectarConfig;
|
|
564
|
+
declare class ConfigError extends Error {
|
|
565
|
+
readonly file: string;
|
|
566
|
+
readonly detail: string;
|
|
567
|
+
constructor(file: string, detail: string);
|
|
568
|
+
}
|
|
569
|
+
/** Checks a loaded config's shape. Discord validates intent and partial values itself at login. */
|
|
570
|
+
declare function validateConfig(value: unknown, file: string): NectarConfig;
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/define.d.ts
|
|
573
|
+
type Empty = Record<never, never>;
|
|
574
|
+
/** `NectarRoutes[K]` when the generated types declare it, otherwise `never`. */
|
|
575
|
+
type Declared<K extends string> = NectarRoutes extends Record<K, infer V> ? V : never;
|
|
576
|
+
type Fallback<T, F> = [T] extends [never] ? F : T;
|
|
577
|
+
type ComponentKindName = "button" | "modal" | `select:${SelectKind}`;
|
|
578
|
+
/** Component routes by path. Until types are generated, any string is accepted. */
|
|
579
|
+
type ComponentRoutes = Fallback<Declared<"components">, Record<string, {
|
|
580
|
+
kind: ComponentKindName;
|
|
581
|
+
params: Params;
|
|
582
|
+
context: Empty;
|
|
583
|
+
}>>;
|
|
584
|
+
/** Command routes by path. Until types are generated, any string is accepted. */
|
|
585
|
+
type CommandRoutes = Fallback<Declared<"commands">, Record<string, {
|
|
586
|
+
type: CommandType;
|
|
587
|
+
options: Record<string, OptionType>;
|
|
588
|
+
context: Empty;
|
|
589
|
+
}>>;
|
|
590
|
+
type ComponentPath = keyof ComponentRoutes & string;
|
|
591
|
+
type CommandPath = keyof CommandRoutes & string;
|
|
592
|
+
type Route<Routes, P extends string> = P extends keyof Routes ? Routes[P] : never;
|
|
593
|
+
type ComponentInteraction<K> = K extends "button" ? ButtonInteraction : K extends "modal" ? ModalSubmitInteraction : K extends "select:string" ? StringSelectMenuInteraction : K extends "select:user" ? UserSelectMenuInteraction : K extends "select:role" ? RoleSelectMenuInteraction : K extends "select:channel" ? ChannelSelectMenuInteraction : K extends "select:mentionable" ? MentionableSelectMenuInteraction : never;
|
|
594
|
+
type CommandInteraction<T> = T extends "chatInput" ? ChatInputCommandInteraction : T extends "user" ? UserContextMenuCommandInteraction : T extends "message" ? MessageContextMenuCommandInteraction : never;
|
|
595
|
+
type Field<R, K extends string, F> = R extends Record<K, infer V> ? V : F;
|
|
596
|
+
type ComponentParams<P extends ComponentPath> = Field<Route<ComponentRoutes, P>, "params", Params>;
|
|
597
|
+
type ComponentContext<P extends ComponentPath> = InteractionContext<ComponentInteraction<Field<Route<ComponentRoutes, P>, "kind", ComponentKindName>>, ComponentParams<P>> & Field<Route<ComponentRoutes, P>, "context", Empty>;
|
|
598
|
+
type CommandContext<P extends CommandPath> = InteractionContext<CommandInteraction<Field<Route<CommandRoutes, P>, "type", CommandType>>, Empty> & Field<Route<CommandRoutes, P>, "context", Empty>;
|
|
599
|
+
/** Every field is optional when the route has no parameters, so `customId("confirm")` works. */
|
|
600
|
+
type ParamsArg<P extends ComponentPath> = Empty extends ComponentParams<P> ? [params?: ComponentParams<P>] : [params: ComponentParams<P>];
|
|
601
|
+
/**
|
|
602
|
+
* The custom ID for a component route, ready for a discord.js builder.
|
|
603
|
+
*
|
|
604
|
+
* Typed against the generated route map: the path must exist and the parameters must match
|
|
605
|
+
* the dynamic segments. Throws when a value is missing or the ID would exceed Discord's limit.
|
|
606
|
+
*/
|
|
607
|
+
declare function customId<P extends ComponentPath>(route: P, ...args: ParamsArg<P>): string;
|
|
608
|
+
/** A handler that remembers which route it was written for, so the compiler can check the file location. */
|
|
609
|
+
type Routed<F> = F & {
|
|
610
|
+
route: string;
|
|
611
|
+
};
|
|
612
|
+
declare function defineCommand<P extends CommandPath>(route: P, handler: (ctx: CommandContext<P>) => unknown): Routed<typeof handler>;
|
|
613
|
+
interface ComponentOptions<P extends ComponentPath> {
|
|
614
|
+
/**
|
|
615
|
+
* Validators for the route's parameters, run on every incoming custom ID before middleware.
|
|
616
|
+
* A parameter without one accepts any string. When a validator fails the interaction is
|
|
617
|
+
* dropped and reported; the handler never sees it.
|
|
618
|
+
*/
|
|
619
|
+
params?: { [K in keyof ComponentParams<P>]?: ParamValidator<ComponentParams<P>[K]>; };
|
|
620
|
+
}
|
|
621
|
+
declare function defineComponent<P extends ComponentPath>(route: P, handler: (ctx: ComponentContext<P>) => unknown, options?: ComponentOptions<P>): Routed<typeof handler>;
|
|
622
|
+
declare function defineEvent<Name extends keyof ClientEvents>(event: Name, handler: (...args: [...ClientEvents[Name], EventContext]) => unknown): Routed<typeof handler>;
|
|
623
|
+
/**
|
|
624
|
+
* `return next({ member })` types `member` onto every downstream context. Middleware that
|
|
625
|
+
* calls `next()` without returning it adds nothing; pass the extension type explicitly if
|
|
626
|
+
* you need both.
|
|
627
|
+
*/
|
|
628
|
+
declare function defineMiddleware<E extends ContextExtension = Empty>(middleware: (ctx: InteractionContext, next: Next) => Promise<Extended<E> | undefined | void> | Extended<E> | undefined | void): Middleware<E>;
|
|
629
|
+
declare function defineError(handler: ErrorHandler): ErrorHandler;
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region src/events/intents.d.ts
|
|
632
|
+
type Intent = keyof typeof GatewayIntentBits;
|
|
633
|
+
/** The intents an event route depends on, for tooling. Empty when it needs none. */
|
|
634
|
+
declare function requiredIntents(event: string): readonly Intent[];
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/policy.d.ts
|
|
637
|
+
/**
|
|
638
|
+
* Opt-in policy middleware. Each returns a middleware that stops the chain and answers the
|
|
639
|
+
* user with a short ephemeral message when the check fails. Registration-time permissions
|
|
640
|
+
* (`meta.defaultMemberPermissions`) are a separate concept: Discord enforces those before the
|
|
641
|
+
* interaction reaches the bot, and server admins can override them. These checks run in the
|
|
642
|
+
* bot and cannot be overridden.
|
|
643
|
+
*
|
|
644
|
+
* Use them from a `middleware.ts`:
|
|
645
|
+
*
|
|
646
|
+
* export default requirePermissions("BanMembers");
|
|
647
|
+
*
|
|
648
|
+
* Or compose them with your own middleware by calling them inside it.
|
|
649
|
+
*/
|
|
650
|
+
interface PolicyOptions {
|
|
651
|
+
/** What the user sees when the check fails. */
|
|
652
|
+
message?: string;
|
|
653
|
+
}
|
|
654
|
+
/** Passes only interactions that come from a guild. */
|
|
655
|
+
declare function guildOnly(options?: PolicyOptions): Middleware;
|
|
656
|
+
/** Passes only when the invoking member has every listed permission in the current channel. */
|
|
657
|
+
declare function requirePermissions(permissions: PermissionResolvable, options?: PolicyOptions): Middleware;
|
|
658
|
+
interface RoleOptions extends PolicyOptions {
|
|
659
|
+
/** `"any"` (default) passes with one matching role; `"all"` needs every listed role. */
|
|
660
|
+
mode?: "any" | "all";
|
|
661
|
+
}
|
|
662
|
+
/** Passes only when the invoking member holds the listed role IDs. */
|
|
663
|
+
declare function requireRoles(roles: string | readonly string[], options?: RoleOptions): Middleware;
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region src/registration/diff.d.ts
|
|
666
|
+
interface CommandDiff {
|
|
667
|
+
/** Command names, `type:name` when the type is not chat input. */
|
|
668
|
+
added: string[];
|
|
669
|
+
removed: string[];
|
|
670
|
+
changed: string[];
|
|
671
|
+
unchanged: string[];
|
|
672
|
+
hasChanges: boolean;
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/registration/remote.d.ts
|
|
676
|
+
/** The two calls registration needs. discord.js's `REST` satisfies this. */
|
|
677
|
+
interface CommandRest {
|
|
678
|
+
get(route: `/${string}`): Promise<unknown>;
|
|
679
|
+
put(route: `/${string}`, options: {
|
|
680
|
+
body: unknown;
|
|
681
|
+
}): Promise<unknown>;
|
|
682
|
+
}
|
|
683
|
+
type Scope = "global" | {
|
|
684
|
+
guild: string;
|
|
685
|
+
};
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/registration/errors.d.ts
|
|
688
|
+
interface RegistrationProblem {
|
|
689
|
+
/** Command name, or `null` when Discord rejected the request as a whole. */
|
|
690
|
+
command: string | null;
|
|
691
|
+
/** Dotted path inside the command, with option and choice indices replaced by their names. */
|
|
692
|
+
field: string;
|
|
693
|
+
message: string;
|
|
694
|
+
}
|
|
695
|
+
/** Discord rejected a bulk overwrite. Wraps the `DiscordAPIError` with per-command detail. */
|
|
696
|
+
declare class RegistrationError extends Error {
|
|
697
|
+
readonly scope: Scope;
|
|
698
|
+
readonly problems: RegistrationProblem[];
|
|
699
|
+
readonly cause: unknown;
|
|
700
|
+
constructor(scope: Scope, problems: RegistrationProblem[], cause: unknown);
|
|
701
|
+
/** `null` when `error` is not a Discord API error. */
|
|
702
|
+
static from(error: unknown, scope: Scope, commands: RESTPostAPIApplicationCommandsJSONBody[]): RegistrationError | null;
|
|
703
|
+
}
|
|
704
|
+
//#endregion
|
|
705
|
+
//#region src/registration/sync.d.ts
|
|
706
|
+
interface SyncOptions {
|
|
707
|
+
rest: CommandRest;
|
|
708
|
+
applicationId: string;
|
|
709
|
+
commands: RESTPostAPIApplicationCommandsJSONBody[];
|
|
710
|
+
scopes: Scope[];
|
|
711
|
+
/** Directory holding `registration.json`. No cache when omitted. */
|
|
712
|
+
cacheDir?: string;
|
|
713
|
+
/** Compute diffs and report, but write nothing to Discord or the cache. */
|
|
714
|
+
dryRun?: boolean;
|
|
715
|
+
/** Proceed past the safety guard. */
|
|
716
|
+
force?: boolean;
|
|
717
|
+
}
|
|
718
|
+
interface ScopeSync {
|
|
719
|
+
scope: Scope;
|
|
720
|
+
/** `null` when the cache proved nothing changed and Discord was not consulted. */
|
|
721
|
+
diff: CommandDiff | null;
|
|
722
|
+
/** A bulk overwrite was sent. */
|
|
723
|
+
applied: boolean;
|
|
724
|
+
}
|
|
725
|
+
interface SyncResult {
|
|
726
|
+
scopes: ScopeSync[];
|
|
727
|
+
/** Why the guard would block this sync. Empty when it is safe. */
|
|
728
|
+
unsafe: string[];
|
|
729
|
+
}
|
|
730
|
+
/** Thrown instead of applying when the guard trips and `force` is not set. */
|
|
731
|
+
declare class UnsafeSyncError extends Error {
|
|
732
|
+
readonly reasons: string[];
|
|
733
|
+
constructor(reasons: string[]);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Reconciles every scope: read remote, diff, bulk overwrite only when something differs.
|
|
737
|
+
* All scopes are read and checked before any is written, so a guard failure changes nothing.
|
|
738
|
+
*/
|
|
739
|
+
declare function syncCommands(options: SyncOptions): Promise<SyncResult>;
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/index.d.ts
|
|
742
|
+
/**
|
|
743
|
+
* Filled in by the generated `.nectar/types.d.ts` through module augmentation. Until then every
|
|
744
|
+
* route path is accepted and parameters are untyped.
|
|
745
|
+
*/
|
|
746
|
+
interface NectarRoutes {}
|
|
747
|
+
/**
|
|
748
|
+
* What plugins put on `ctx.services`. A plugin declares its entry through module augmentation:
|
|
749
|
+
*
|
|
750
|
+
* declare module "@nectar-js/nectar" {
|
|
751
|
+
* interface NectarServices { audit: AuditLog }
|
|
752
|
+
* }
|
|
753
|
+
*/
|
|
754
|
+
interface NectarServices {}
|
|
755
|
+
//#endregion
|
|
756
|
+
export { InteractionContext as $, defineComponent as A, OptionChoice as At, PluginChange as B, ComponentOptions as C, ChannelOption as Ct, Routed as D, CommandType as Dt, ComponentRoutes as E, CommandRouteMeta as Et, NectarConfig as F, version as Ft, definePlugin as G, PluginCommandContext as H, defineConfig as I, ContextExtension as J, EventMeta as K, validateConfig as L, defineEvent as M, SimpleOption as Mt, defineMiddleware as N, StringOption as Nt, customId as O, IntegerOption as Ot, ConfigError as P, TopLevelMeta as Pt, Extended as Q, NectarPlugin as R, ComponentKindName as S, SelectKind as St, ComponentPath as T, CommandOption as Tt, PluginError as U, PluginCommand as V, PluginGraph as W, ErrorHandler as X, Env as Y, EventContext as Z, requiredIntents as _, LoggerOptions as _t, SyncResult as a, RouteInfo as at, CommandRoutes as b, CustomIdTooLongError as bt, RegistrationError as c, RejectReason as ct, CommandDiff as d, SignalEmitter as dt, Logger as et, PolicyOptions as f, SignalType as ft, requireRoles as g, LogSink as gt, requirePermissions as h, LogRecord as ht, SyncOptions as i, Params as it, defineError as j, OptionType as jt, defineCommand as k, NumberOption as kt, RegistrationProblem as l, Signal as lt, guildOnly as m, LogLevel as mt, NectarServices as n, MiddlewareExtension as nt, UnsafeSyncError as o, Trace as ot, RoleOptions as p, LogFields as pt, EventMode as q, ScopeSync as r, Next as rt, syncCommands as s, InteractionMeta as st, NectarRoutes as t, Middleware as tt, Scope as u, SignalData as ut, CommandContext as v, ParamValidator as vt, ComponentParams as w, CommandMeta as wt, ComponentContext as x, MAX_CUSTOM_ID_LENGTH as xt, CommandPath as y, StandardSchemaLike as yt, PluginApp as z };
|
|
757
|
+
//# sourceMappingURL=index-DGMxBsub.d.ts.map
|