@seedcord/core 0.3.0 → 0.4.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/dist/{Bus-DcuUtOEL.mjs → Bus-BVwmKc-A.mjs} +51 -17
- package/dist/Bus-BVwmKc-A.mjs.map +1 -0
- package/dist/Pluggable-Cf5-WWmW.mjs +280 -0
- package/dist/Pluggable-Cf5-WWmW.mjs.map +1 -0
- package/dist/Pluggable-DFNUIlJM.d.mts +148 -0
- package/dist/{Plugin-Bkeqcjy6.mjs → Plugin-C6oMqWf8.mjs} +32 -7
- package/dist/Plugin-C6oMqWf8.mjs.map +1 -0
- package/dist/Plugin-CG9pWbPF.d.mts +193 -0
- package/dist/{cursor-DKKKUa_7.d.mts → cursor-BcRfmTSo.d.mts} +27 -140
- package/dist/{cursor-Cv1Ui6gZ.mjs → cursor-GBUYz3jk.mjs} +377 -176
- package/dist/cursor-GBUYz3jk.mjs.map +1 -0
- package/dist/{index-uBwh8r7x.d.mts → index-ZzdQafFv.d.mts} +2 -2
- package/dist/index.d.mts +132 -19
- package/dist/index.mjs +186 -19
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +11 -4
- package/dist/internal.index.mjs +8 -455
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +10 -146
- package/dist/node-internal.index.mjs +43 -470
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +2 -0
- package/dist/node.index.mjs +256 -0
- package/dist/node.index.mjs.map +1 -0
- package/dist/{Plugin-B6iRzm3g.d.mts → phases-F8yvPd60.d.mts} +111 -207
- package/dist/plugin.index.d.mts +2 -2
- package/dist/plugin.index.mjs +1 -1
- package/dist/publishDefault-D826XXDQ.mjs +14 -0
- package/dist/publishDefault-D826XXDQ.mjs.map +1 -0
- package/dist/{slashRouteLeaves-De1SxqnX.mjs → slashRouteLeaves-CbYRwqMY.mjs} +10 -2
- package/dist/slashRouteLeaves-CbYRwqMY.mjs.map +1 -0
- package/dist/subscribers-CGGQkUfM.mjs +139 -0
- package/dist/subscribers-CGGQkUfM.mjs.map +1 -0
- package/package.json +14 -8
- package/dist/Bus-DcuUtOEL.mjs.map +0 -1
- package/dist/Plugin-Bkeqcjy6.mjs.map +0 -1
- package/dist/cursor-Cv1Ui6gZ.mjs.map +0 -1
- package/dist/slashRouteLeaves-De1SxqnX.mjs.map +0 -1
- package/dist/subscribers-DuHg-bGT.mjs +0 -3
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Logger } from "@seedcord/logger";
|
|
2
|
+
import { APIApplicationCommand } from "discord-api-types/v10";
|
|
2
3
|
import { TypedEventEmitter } from "@seedcord/event-emitter";
|
|
3
4
|
import { REST } from "@discordjs/rest";
|
|
4
|
-
import { Config, EventFrequency, FrameworkChannel, HmrAware, HmrUpdateEvent, IRateLimiter, RenderContext, ReplyResponse, Tail, TypedExclude } from "@seedcord/types";
|
|
5
5
|
import { UUID } from "node:crypto";
|
|
6
|
+
import { Config, EventFrequency, IRateLimiter, RenderContext, ReplyResponse, TypedExclude } from "@seedcord/types";
|
|
6
7
|
//#region src/metadataKeys.d.ts
|
|
7
8
|
declare enum InteractionRoutes {
|
|
8
9
|
Slash = "slash",
|
|
@@ -30,6 +31,13 @@ declare const WebhookUrlMetadataKey: unique symbol;
|
|
|
30
31
|
/** @internal PublishableKey excludes this key so publish() never emits it externally. */
|
|
31
32
|
declare const PublishDefault: unique symbol;
|
|
32
33
|
//#endregion
|
|
34
|
+
//#region src/subscribers/slots.d.ts
|
|
35
|
+
declare const RegisterDefaults: unique symbol;
|
|
36
|
+
declare const VerifyWebhooks: unique symbol;
|
|
37
|
+
declare const RegisteredCount: unique symbol;
|
|
38
|
+
declare const RegisterSubscriber: unique symbol;
|
|
39
|
+
declare const UnregisterSubscriber: unique symbol;
|
|
40
|
+
//#endregion
|
|
33
41
|
//#region src/reply/AckTrace.d.ts
|
|
34
42
|
declare class AckTrace extends Error {
|
|
35
43
|
constructor(method: ReplyMethod);
|
|
@@ -103,8 +111,9 @@ declare abstract class Notice extends Error {
|
|
|
103
111
|
*/
|
|
104
112
|
ephemeral: boolean;
|
|
105
113
|
/**
|
|
106
|
-
* A short one-line reason. When every arm of an
|
|
107
|
-
*
|
|
114
|
+
* A short one-line reason. When every arm of an {@link or} gate refuses and each one sets this, `or`
|
|
115
|
+
* throws a refusal listing them under `You need to meet at least one of these:`. Write a verb phrase that
|
|
116
|
+
* finishes that line, for example, "be the bot owner".
|
|
108
117
|
*/
|
|
109
118
|
summary?: string;
|
|
110
119
|
protected constructor(message: string, options?: ErrorOptions);
|
|
@@ -122,8 +131,6 @@ declare abstract class Notice extends Error {
|
|
|
122
131
|
* `Fault`, is `failed`, as is any other throw.
|
|
123
132
|
*/
|
|
124
133
|
type DispatchOutcome = 'handled' | 'refused' | 'failed';
|
|
125
|
-
/** Whether a write through the reply surface reached Discord. */
|
|
126
|
-
type ResponseOutcome = 'sent' | 'failed';
|
|
127
134
|
/**
|
|
128
135
|
* Where a reported fault came from.
|
|
129
136
|
*/
|
|
@@ -132,36 +139,69 @@ type FaultSource = InteractionFaultSource | EventFaultSource;
|
|
|
132
139
|
* A fault that came from a discord interaction, JSON-safe for a webhook payload.
|
|
133
140
|
*/
|
|
134
141
|
interface InteractionFaultSource {
|
|
135
|
-
kind: 'interaction';
|
|
136
|
-
interactionKind: 'slash' | 'context-menu' | 'button' | 'select' | 'modal';
|
|
142
|
+
readonly kind: 'interaction';
|
|
143
|
+
readonly interactionKind: 'slash' | 'context-menu' | 'button' | 'select' | 'modal';
|
|
137
144
|
/** Slash or context-menu command name, null for component and modal interactions. */
|
|
138
|
-
command: string | null;
|
|
145
|
+
readonly command: string | null;
|
|
139
146
|
/** Component or modal customId, null for slash and context-menu interactions. */
|
|
140
|
-
customId: string | null;
|
|
141
|
-
userId: string;
|
|
147
|
+
readonly customId: string | null;
|
|
148
|
+
readonly userId: string;
|
|
142
149
|
/** Guild the interaction came from, null in DMs. */
|
|
143
|
-
guildId: string | null;
|
|
150
|
+
readonly guildId: string | null;
|
|
144
151
|
/** Channel the interaction came from, null when unavailable. */
|
|
145
|
-
channelId: string | null;
|
|
146
|
-
interactionId: string;
|
|
152
|
+
readonly channelId: string | null;
|
|
153
|
+
readonly interactionId: string;
|
|
147
154
|
/** The raw interaction, made JSON-safe at serialization with filterCirculars. */
|
|
148
|
-
raw: unknown;
|
|
155
|
+
readonly raw: unknown;
|
|
149
156
|
}
|
|
150
157
|
/**
|
|
151
158
|
* A fault that came from a client event, JSON-safe for a webhook payload. The actor, guild, and channel
|
|
152
159
|
* are best-effort, derived from the event args. Each is nullable. The raw args carry the full detail.
|
|
153
160
|
*/
|
|
154
161
|
interface EventFaultSource {
|
|
155
|
-
kind: 'event';
|
|
156
|
-
eventName: string;
|
|
157
|
-
handler: string;
|
|
158
|
-
userId: string | null;
|
|
162
|
+
readonly kind: 'event';
|
|
163
|
+
readonly eventName: string;
|
|
164
|
+
readonly handler: string;
|
|
165
|
+
readonly userId: string | null;
|
|
159
166
|
/** Null outside a guild. */
|
|
160
|
-
guildId: string | null;
|
|
161
|
-
channelId: string | null;
|
|
167
|
+
readonly guildId: string | null;
|
|
168
|
+
readonly channelId: string | null;
|
|
162
169
|
/** The raw event args, made JSON-safe at serialization with filterCirculars. */
|
|
163
|
-
raw: unknown;
|
|
170
|
+
readonly raw: unknown;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The fields both `responseAttempted` arms carry.
|
|
174
|
+
*/
|
|
175
|
+
interface AttemptedWrite {
|
|
176
|
+
readonly routeId: string;
|
|
177
|
+
/** The interaction this write belongs to, for joining against `interactionDispatched`. */
|
|
178
|
+
readonly interactionId: string;
|
|
179
|
+
/** `send` routes to another verb, so it reports the verb that ran. */
|
|
180
|
+
readonly method: WriteMethod;
|
|
181
|
+
readonly durationMs: number;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* A write Discord accepted.
|
|
185
|
+
*/
|
|
186
|
+
interface ResponseSent extends AttemptedWrite {
|
|
187
|
+
readonly outcome: 'sent';
|
|
188
|
+
/** Null for every ack-only and choice-only verb. */
|
|
189
|
+
readonly messageId: string | null;
|
|
164
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* A write that threw. A non-Error throw arrives wrapped, carrying the thrown value as its `cause`.
|
|
193
|
+
*/
|
|
194
|
+
interface ResponseFailed extends AttemptedWrite {
|
|
195
|
+
readonly outcome: 'failed';
|
|
196
|
+
readonly messageId: null;
|
|
197
|
+
readonly error: Error;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* One write through the reply surface. Check `outcome` to reach `error`.
|
|
201
|
+
*/
|
|
202
|
+
type ResponseAttempt = ResponseSent | ResponseFailed;
|
|
203
|
+
/** Whether a write through the reply surface reached Discord. */
|
|
204
|
+
type ResponseOutcome = ResponseAttempt['outcome'];
|
|
165
205
|
/**
|
|
166
206
|
* The framework's own subscription keys. A transport adds a key whose payload type references a type
|
|
167
207
|
* outside this package, through declaration merging on its own module.
|
|
@@ -171,69 +211,63 @@ interface EventFaultSource {
|
|
|
171
211
|
*/
|
|
172
212
|
interface DefaultSubscriptions {
|
|
173
213
|
/** Triggered when an unhandled exception (a raw non-Notice throw) occurs. */
|
|
174
|
-
unknownException: {
|
|
175
|
-
uuid: UUID;
|
|
176
|
-
error: Error;
|
|
214
|
+
readonly unknownException: {
|
|
215
|
+
readonly uuid: UUID;
|
|
216
|
+
readonly error: Error;
|
|
177
217
|
/** Where the throw came from, `slash:ban` for an interaction and `event:name:handler` for an event. */
|
|
178
|
-
routeId: string;
|
|
179
|
-
guild?: {
|
|
180
|
-
id: string;
|
|
181
|
-
name: string;
|
|
218
|
+
readonly routeId: string;
|
|
219
|
+
readonly guild?: {
|
|
220
|
+
readonly id: string;
|
|
221
|
+
readonly name: string;
|
|
182
222
|
} | undefined;
|
|
183
|
-
user?: {
|
|
184
|
-
id: string;
|
|
185
|
-
username: string;
|
|
223
|
+
readonly user?: {
|
|
224
|
+
readonly id: string;
|
|
225
|
+
readonly username: string;
|
|
186
226
|
} | undefined;
|
|
187
|
-
metadata?: unknown;
|
|
227
|
+
readonly metadata?: unknown;
|
|
188
228
|
};
|
|
189
229
|
/** Triggered when a reported Notice (`report: true`) is caught. */
|
|
190
|
-
handledException: {
|
|
191
|
-
denial: Notice;
|
|
192
|
-
uuid: UUID;
|
|
230
|
+
readonly handledException: {
|
|
231
|
+
readonly denial: Notice;
|
|
232
|
+
readonly uuid: UUID;
|
|
193
233
|
/** The same id `interactionDispatched` publishes. An event reads `event:name:handler`. */
|
|
194
|
-
routeId: string;
|
|
195
|
-
source: FaultSource;
|
|
234
|
+
readonly routeId: string;
|
|
235
|
+
readonly source: FaultSource;
|
|
196
236
|
};
|
|
197
237
|
/** Triggered when an interaction dispatch throws past the fault boundary. */
|
|
198
|
-
unhandledInteractionError: {
|
|
199
|
-
error: Error;
|
|
238
|
+
readonly unhandledInteractionError: {
|
|
239
|
+
readonly error: Error;
|
|
200
240
|
};
|
|
201
241
|
/** Triggered once per interaction dispatch, after the handler chain settles. */
|
|
202
|
-
interactionDispatched: {
|
|
203
|
-
routeId: string;
|
|
242
|
+
readonly interactionDispatched: {
|
|
243
|
+
readonly routeId: string;
|
|
204
244
|
/** The interaction this dispatch ran, for joining against `responseAttempted`. */
|
|
205
|
-
interactionId: string;
|
|
206
|
-
kind: `${InteractionRoutes}`;
|
|
207
|
-
outcome: DispatchOutcome;
|
|
245
|
+
readonly interactionId: string;
|
|
246
|
+
readonly kind: `${InteractionRoutes}`;
|
|
247
|
+
readonly outcome: DispatchOutcome;
|
|
208
248
|
/** True when no route matched and the unhandled default ran. */
|
|
209
|
-
fallback: boolean;
|
|
249
|
+
readonly fallback: boolean;
|
|
210
250
|
/** Dispatch entry until the user has a response, replies included. A clock change never affects it. */
|
|
211
|
-
durationMs: number;
|
|
251
|
+
readonly durationMs: number;
|
|
212
252
|
/**
|
|
213
253
|
* Discord's interaction creation until dispatch entry, read from the snowflake. This covers
|
|
214
254
|
* network transit plus any time your bot was busy before starting. A rise points at either
|
|
215
255
|
* Discord or your own backlog. It subtracts a Discord timestamp from the host clock, so a host
|
|
216
256
|
* running behind Discord reports a negative value.
|
|
217
257
|
*/
|
|
218
|
-
queuedMs: number;
|
|
258
|
+
readonly queuedMs: number;
|
|
219
259
|
};
|
|
220
260
|
/**
|
|
221
261
|
* Triggered on every write through the reply surface, several times per interaction. A write that
|
|
222
262
|
* throws reports here too, with `outcome` `failed`. A write that reaches Discord and then returns an
|
|
223
263
|
* unexpected shape publishes nothing, since the callback succeeded and carries no message to name.
|
|
224
264
|
*/
|
|
225
|
-
responseAttempted:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
|
|
231
|
-
outcome: ResponseOutcome;
|
|
232
|
-
durationMs: number;
|
|
233
|
-
/** Null for every ack-only or choice-only verb, and for a failed write. */
|
|
234
|
-
messageId: string | null;
|
|
235
|
-
/** Present when `outcome` is `failed`. */
|
|
236
|
-
error?: Error;
|
|
265
|
+
readonly responseAttempted: ResponseAttempt;
|
|
266
|
+
/** Triggered after each command deploy, including the redeploy after a hot reload. */
|
|
267
|
+
readonly commandsDeployed: {
|
|
268
|
+
readonly global: readonly APIApplicationCommand[];
|
|
269
|
+
/** Keyed by guild id. */
|
|
270
|
+
readonly guilds: Readonly<Record<string, readonly APIApplicationCommand[]>>;
|
|
237
271
|
};
|
|
238
272
|
}
|
|
239
273
|
/**
|
|
@@ -317,7 +351,7 @@ interface SubscriberRegistration {
|
|
|
317
351
|
/** A server-host registration carries it from the start. A lazy one fills it on first resolve. */
|
|
318
352
|
ctor?: StoredSubscriberCtor | undefined;
|
|
319
353
|
}
|
|
320
|
-
declare const loggerSlot
|
|
354
|
+
declare const loggerSlot: unique symbol;
|
|
321
355
|
/**
|
|
322
356
|
* Registers and dispatches subscribers. Subscribers run on a programmatic or framework publish.
|
|
323
357
|
* Accessed via `core.bus`. Do not construct it directly.
|
|
@@ -326,21 +360,21 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
|
|
|
326
360
|
protected core: CoreBase;
|
|
327
361
|
private readonly logger;
|
|
328
362
|
/** @internal */
|
|
329
|
-
readonly [loggerSlot
|
|
363
|
+
readonly [loggerSlot]: Logger;
|
|
330
364
|
private readonly subscribersMap;
|
|
331
365
|
private readonly executedOnce;
|
|
332
366
|
private readonly webhookProbes;
|
|
333
367
|
constructor(core: CoreBase);
|
|
334
|
-
/** @internal
|
|
335
|
-
|
|
368
|
+
/** @internal keep this inside start(). a bad webhook url thrown outside it never resets the singleton guard */
|
|
369
|
+
[RegisterDefaults](): void;
|
|
336
370
|
/** @internal */
|
|
337
|
-
|
|
371
|
+
[RegisterSubscriber](registration: SubscriberRegistration): void;
|
|
338
372
|
/** @internal */
|
|
339
|
-
|
|
373
|
+
[UnregisterSubscriber](ctor: StoredSubscriberCtor, keys?: readonly SubscriptionKey[]): void;
|
|
340
374
|
/** @internal */
|
|
341
|
-
get
|
|
342
|
-
/** @internal
|
|
343
|
-
|
|
375
|
+
get [RegisteredCount](): number;
|
|
376
|
+
/** @internal a lazy edge registration never probes. every server-host reporter registers eagerly */
|
|
377
|
+
[VerifyWebhooks](): Promise<void>;
|
|
344
378
|
private probeWebhook;
|
|
345
379
|
/**
|
|
346
380
|
* Publishes an event to its subscribers and native listeners.
|
|
@@ -359,6 +393,11 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
|
|
|
359
393
|
* @returns Whether the underlying emitter had native listeners for the event
|
|
360
394
|
*/
|
|
361
395
|
publish<KeyOfSubscribers extends PublishableKey>(event: KeyOfSubscribers, data: AllSubscriptions[KeyOfSubscribers]): boolean;
|
|
396
|
+
/**
|
|
397
|
+
* @deprecated Always throws. Call {@link Bus.publish} to reach subscribers and listeners together.
|
|
398
|
+
* @throws A **SeedcordTypeError** with `CoreBusEmitUnavailable`. `emit` reaches only the `on()` listeners.
|
|
399
|
+
*/
|
|
400
|
+
emit<KeyOfSubscribers extends SubscriptionKey>(event: KeyOfSubscribers, ..._args: SubscriptionTuples[KeyOfSubscribers]): never;
|
|
362
401
|
/** @internal the framework's own publish path, keyed by a symbol no public entry exports */
|
|
363
402
|
[PublishDefault]<KeyOfSubscribers extends keyof DefaultSubscriptions>(event: KeyOfSubscribers, data: AllSubscriptions[KeyOfSubscribers]): boolean;
|
|
364
403
|
private dispatchKey;
|
|
@@ -383,16 +422,14 @@ interface CoreBase {
|
|
|
383
422
|
* until the Login phase. On http the token is set by `start()`.
|
|
384
423
|
*/
|
|
385
424
|
readonly rest: REST;
|
|
425
|
+
/** The bot's Discord application id. Throws if you read it before the host resolves it. */
|
|
426
|
+
readonly applicationId: string;
|
|
386
427
|
/** Sliding-window rate limiting, the `Cooldown` gate's backend and a public direct-use API. */
|
|
387
428
|
readonly rateLimiter: IRateLimiter;
|
|
388
429
|
/** Publish and subscribe to framework and application events. */
|
|
389
430
|
readonly bus: Bus;
|
|
390
431
|
}
|
|
391
432
|
//#endregion
|
|
392
|
-
//#region src/plugin/brands.d.ts
|
|
393
|
-
declare const TransportBrand: unique symbol;
|
|
394
|
-
declare const RuntimeBrand: unique symbol;
|
|
395
|
-
//#endregion
|
|
396
433
|
//#region src/lifecycle/phases.d.ts
|
|
397
434
|
/**
|
|
398
435
|
* Startup phases, in run order.
|
|
@@ -419,138 +456,5 @@ declare enum ShutdownPhase {
|
|
|
419
456
|
Logout = 4
|
|
420
457
|
}
|
|
421
458
|
//#endregion
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
* The lifecycle a plugin declares through `super(host, spec)`. Unset fields resolve to the base
|
|
425
|
-
* defaults. `ready` runs at a fixed `StartupPhase.Ready`.
|
|
426
|
-
*/
|
|
427
|
-
interface PluginLifecycleSpec {
|
|
428
|
-
init?: {
|
|
429
|
-
phase?: StartupPhase;
|
|
430
|
-
timeout?: number;
|
|
431
|
-
};
|
|
432
|
-
ready?: {
|
|
433
|
-
timeout?: number;
|
|
434
|
-
};
|
|
435
|
-
dispose?: {
|
|
436
|
-
phase?: ShutdownPhase;
|
|
437
|
-
timeout?: number;
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
/** @internal */
|
|
441
|
-
interface ResolvedPluginLifecycleSpec {
|
|
442
|
-
readonly init: {
|
|
443
|
-
readonly phase: StartupPhase;
|
|
444
|
-
readonly timeout: number;
|
|
445
|
-
};
|
|
446
|
-
readonly ready: {
|
|
447
|
-
readonly timeout: number;
|
|
448
|
-
};
|
|
449
|
-
readonly dispose: {
|
|
450
|
-
readonly phase: ShutdownPhase;
|
|
451
|
-
readonly timeout: number;
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
//#endregion
|
|
455
|
-
//#region src/plugin/options.d.ts
|
|
456
|
-
/** The options a plugin declares as its `Plugin<Opts>` type argument. */
|
|
457
|
-
interface PluginOptions {
|
|
458
|
-
/**
|
|
459
|
-
* Which transport the plugin runs on. `'any'` attaches to either one.
|
|
460
|
-
* @defaultValue 'any'
|
|
461
|
-
*/
|
|
462
|
-
transport?: 'gateway' | 'http' | 'any';
|
|
463
|
-
/**
|
|
464
|
-
* Which runtime the plugin runs on. `'any'` attaches to either one.
|
|
465
|
-
* @defaultValue 'any'
|
|
466
|
-
*/
|
|
467
|
-
runtime?: 'server' | 'edge' | 'any';
|
|
468
|
-
}
|
|
469
|
-
/** @internal */
|
|
470
|
-
type TransportOf<Opts extends PluginOptions> = undefined extends Opts['transport'] ? 'any' : NonNullable<Opts['transport']>;
|
|
471
|
-
/** @internal */
|
|
472
|
-
type RuntimeOf<Opts extends PluginOptions> = undefined extends Opts['runtime'] ? 'any' : NonNullable<Opts['runtime']>;
|
|
473
|
-
/** @internal */
|
|
474
|
-
type Transport = TypedExclude<NonNullable<PluginOptions['transport']>, 'any'>;
|
|
475
|
-
/** @internal */
|
|
476
|
-
type Runtime = TypedExclude<NonNullable<PluginOptions['runtime']>, 'any'>;
|
|
477
|
-
type TransportMismatch<PluginT extends string, BotT extends string> = Record<`this plugin declares transport '${PluginT}' but this bot runs '${BotT}'`, never>;
|
|
478
|
-
type RuntimeMismatch<PluginRt extends string, BotRt extends string> = Record<`this plugin declares runtime '${PluginRt}' but this bot runs '${BotRt}'`, never>;
|
|
479
|
-
type EdgePluginsUnsupported = Record<'an edge bot takes no plugins until edge support ships after v1', never>;
|
|
480
|
-
/** @internal */
|
|
481
|
-
type ChannelKeyAssert<Key extends string> = Key extends FrameworkChannel ? `'${Key}' is a channel the framework logs on. Pick another plugin key.` : Key;
|
|
482
|
-
type BrandTransport<Plug> = Plug extends {
|
|
483
|
-
readonly [TransportBrand]?: infer T extends string;
|
|
484
|
-
} ? T : 'any';
|
|
485
|
-
type BrandRuntime<Plug> = Plug extends {
|
|
486
|
-
readonly [RuntimeBrand]?: infer R extends string;
|
|
487
|
-
} ? R : 'any';
|
|
488
|
-
/** @internal */
|
|
489
|
-
type TransportAssert<Plug, BotT extends Transport> = BrandTransport<Plug> extends 'any' | BotT ? unknown : TransportMismatch<BrandTransport<Plug>, BotT>;
|
|
490
|
-
/** @internal */
|
|
491
|
-
type RuntimeAssert<Plug, BotRt extends Runtime> = 'edge' extends BotRt ? EdgePluginsUnsupported : BrandRuntime<Plug> extends 'any' | BotRt ? unknown : RuntimeMismatch<BrandRuntime<Plug>, BotRt>;
|
|
492
|
-
//#endregion
|
|
493
|
-
//#region src/plugin/Plugin.d.ts
|
|
494
|
-
interface Initializeable {
|
|
495
|
-
init(): Promise<void>;
|
|
496
|
-
}
|
|
497
|
-
/** @internal */
|
|
498
|
-
declare const resolvedSpecSlot: unique symbol;
|
|
499
|
-
/** @internal */
|
|
500
|
-
declare const loggerSlot: unique symbol;
|
|
501
|
-
/**
|
|
502
|
-
* Base class for Seedcord plugins. A subclass declares its options as the `Opts` type argument,
|
|
503
|
-
* implements `init()`, and reads the framework surface through `this.core`.
|
|
504
|
-
*
|
|
505
|
-
* @typeParam Opts - The plugin's declared {@link PluginOptions}.
|
|
506
|
-
* @typeParam TCore - The host type `this.core` resolves to. Each transport binds it to its own `Core`.
|
|
507
|
-
*/
|
|
508
|
-
declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase> implements Initializeable, HmrAware {
|
|
509
|
-
private readonly host;
|
|
510
|
-
/** @internal */
|
|
511
|
-
readonly [TransportBrand]?: TransportOf<Opts>;
|
|
512
|
-
/** @internal */
|
|
513
|
-
readonly [RuntimeBrand]?: RuntimeOf<Opts>;
|
|
514
|
-
/** @internal */
|
|
515
|
-
readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;
|
|
516
|
-
/** @internal */
|
|
517
|
-
readonly [loggerSlot]: Logger;
|
|
518
|
-
/** Logs under the plugin's class name, on the channel its attach key sets. */
|
|
519
|
-
protected readonly logger: Logger;
|
|
520
|
-
constructor(host: CoreBase, spec?: PluginLifecycleSpec);
|
|
521
|
-
/** The host, typed to the transport whose `Plugin` base this class extends. */
|
|
522
|
-
protected get core(): TCore;
|
|
523
|
-
abstract init(): Promise<void>;
|
|
524
|
-
/**
|
|
525
|
-
* Runs in the startup Ready phase, after `init()`. The gateway client is logged in by this phase,
|
|
526
|
-
* and the http host binds its server in the same phase.
|
|
527
|
-
*/
|
|
528
|
-
ready?(): Promise<void>;
|
|
529
|
-
/** Runs during teardown, only when `init()` resolved. */
|
|
530
|
-
dispose?(): Promise<void>;
|
|
531
|
-
/**
|
|
532
|
-
* Throws a `SeedcordError` that contains the plugin class name and the reason.
|
|
533
|
-
*
|
|
534
|
-
* @param reason - Why the options are invalid.
|
|
535
|
-
*/
|
|
536
|
-
protected rejectOptions(reason: string): never;
|
|
537
|
-
/** Override to reload plugin state on an HMR update. */
|
|
538
|
-
onHmr(_event: HmrUpdateEvent): Promise<void>;
|
|
539
|
-
/**
|
|
540
|
-
* Registers critical file patterns that should trigger a full restart when changed in Dev HMR.
|
|
541
|
-
* @param patterns - Glob patterns relative to the project root
|
|
542
|
-
*/
|
|
543
|
-
protected registerCriticalFiles(patterns: string[]): void;
|
|
544
|
-
}
|
|
545
|
-
/** @internal */
|
|
546
|
-
type PluginLike = Pick<Plugin, 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot>;
|
|
547
|
-
/** @internal */
|
|
548
|
-
type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;
|
|
549
|
-
/** @internal */
|
|
550
|
-
type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;
|
|
551
|
-
type CoreParamTooNarrow = Record<'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core', never>;
|
|
552
|
-
/** @internal */
|
|
553
|
-
type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0] ? unknown : CoreParamTooNarrow;
|
|
554
|
-
//#endregion
|
|
555
|
-
export { reportedWrite as A, InteractionMetadataKey as B, DispatchOutcome as C, Subscriptions as D, SubscriptionKey as E, AckTrace as F, WebhookUrlMetadataKey as G, InteractionRoutes as H, PublishDefault as I, CommandMetadataKey as L, ReplyMethod as M, checkAckLegality as N, Notice as O, sendTarget as P, EventMetadataKey as R, DefaultSubscriptions as S, SubscriptionData as T, MiddlewareMetadataKey as U, InteractionRouteKeys as V, SubscribeMetadataKey as W, Bus as _, PluginCtor as a, busLoggerOf as b, PluginOptions as c, Transport as d, TransportAssert as f, CoreBase as g, StartupPhase as h, PluginArgs as i, AckState as j, ReplyTelemetry as k, Runtime as l, ShutdownPhase as m, Initializeable as n, PluginLike as o, PluginLifecycleSpec as p, Plugin as r, ChannelKeyAssert as s, CoreParamAssert as t, RuntimeAssert as u, StoredSubscriberCtor as v, FaultSource as w, Subscriber as x, SubscriberRegistration as y, GatedMetadataKey as z };
|
|
556
|
-
//# sourceMappingURL=Plugin-B6iRzm3g.d.mts.map
|
|
459
|
+
export { RegisteredCount as A, MiddlewareMetadataKey as B, AckState as C, AckTrace as D, sendTarget as E, EventMetadataKey as F, WebhookUrlMetadataKey as H, GatedMetadataKey as I, InteractionMetadataKey as L, VerifyWebhooks as M, PublishDefault as N, RegisterDefaults as O, CommandMetadataKey as P, InteractionRouteKeys as R, reportedWrite as S, checkAckLegality as T, SubscribeMetadataKey as V, SubscriptionData as _, StoredSubscriberCtor as a, Notice as b, Subscriber as c, DispatchOutcome as d, FaultSource as f, ResponseSent as g, ResponseOutcome as h, Bus as i, UnregisterSubscriber as j, RegisterSubscriber as k, AttemptedWrite as l, ResponseFailed as m, StartupPhase as n, SubscriberRegistration as o, ResponseAttempt as p, CoreBase as r, busLoggerOf as s, ShutdownPhase as t, DefaultSubscriptions as u, SubscriptionKey as v, ReplyMethod as w, ReplyTelemetry as x, Subscriptions as y, InteractionRoutes as z };
|
|
460
|
+
//# sourceMappingURL=phases-F8yvPd60.d.mts.map
|
package/dist/plugin.index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { c as PluginOptions,
|
|
1
|
+
import { c as PluginOptions, p as PluginLifecycleSpec, r as Plugin } from "./Plugin-CG9pWbPF.mjs";
|
|
2
2
|
import { HmrAware, HmrUpdateEvent } from "@seedcord/types";
|
|
3
|
-
export { type HmrAware, type HmrUpdateEvent,
|
|
3
|
+
export { type HmrAware, type HmrUpdateEvent, Plugin, type PluginLifecycleSpec, type PluginOptions };
|
package/dist/plugin.index.mjs
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/stops/asError.ts
|
|
2
|
+
/** @internal */
|
|
3
|
+
function asError(caught) {
|
|
4
|
+
return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/subscribers/publishDefault.ts
|
|
9
|
+
/** @internal PublishableKey excludes this key so publish() never emits it externally. */
|
|
10
|
+
const PublishDefault = Symbol("seedcord.publishDefault");
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { asError as n, PublishDefault as t };
|
|
14
|
+
//# sourceMappingURL=publishDefault-D826XXDQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publishDefault-D826XXDQ.mjs","names":[],"sources":["../src/stops/asError.ts","../src/subscribers/publishDefault.ts"],"sourcesContent":["// dropping a non-Error throw would leave the user with no fault card, and every report payload declares an Error\n/** @internal */\nexport function asError(caught: unknown): Error {\n return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });\n}\n","/** @internal PublishableKey excludes this key so publish() never emits it externally. */\nexport const PublishDefault = Symbol('seedcord.publishDefault');\n"],"mappings":";;AAEA,SAAgB,QAAQ,QAAwB;CAC5C,OAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,IAAI,MAAM,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;AACvF;;;;;ACHA,MAAa,iBAAiB,OAAO,yBAAyB"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { b as CommandMetadataKey, v as isBuilderComponentClass } from "./Bus-BVwmKc-A.mjs";
|
|
1
2
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
2
3
|
import { routeLeavesOf } from "@seedcord/utils/internal";
|
|
3
4
|
|
|
5
|
+
//#region src/commands/isCommandClass.ts
|
|
6
|
+
/** @internal */
|
|
7
|
+
function isCommandClass(value) {
|
|
8
|
+
return isBuilderComponentClass(value) && Reflect.hasMetadata(CommandMetadataKey, value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
4
12
|
//#region src/commands/slashRouteLeaves.ts
|
|
5
13
|
/**
|
|
6
14
|
* dedupes route keys since the same builder appears once per guild.
|
|
@@ -19,5 +27,5 @@ function slashRouteLeaves(commands) {
|
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
//#endregion
|
|
22
|
-
export { slashRouteLeaves as t };
|
|
23
|
-
//# sourceMappingURL=slashRouteLeaves-
|
|
30
|
+
export { isCommandClass as n, slashRouteLeaves as t };
|
|
31
|
+
//# sourceMappingURL=slashRouteLeaves-CbYRwqMY.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slashRouteLeaves-CbYRwqMY.mjs","names":[],"sources":["../src/commands/isCommandClass.ts","../src/commands/slashRouteLeaves.ts"],"sourcesContent":["import { isBuilderComponentClass } from '#components/Component';\nimport { CommandMetadataKey } from '#src/metadataKeys';\n\nimport type { BuilderComponent } from '#components/Component';\n\n/** @internal */\nexport type CommandCtor = new () => BuilderComponent<'command' | 'context_menu'>;\n\n// the cli's codegen scan reads this too\n/** @internal */\nexport function isCommandClass(value: unknown): value is CommandCtor {\n return isBuilderComponentClass(value) && Reflect.hasMetadata(CommandMetadataKey, value);\n}\n","import { SlashCommandBuilder } from '@discordjs/builders';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\n\nimport type { ContextMenuCommandBuilder } from '@discordjs/builders';\n\n/**\n * dedupes route keys since the same builder appears once per guild.\n * these come from the same {@link routeLeavesOf} that `seedcord codegen` reads, so the boot-time\n * check compares against the generated registry's keys.\n *\n * @internal\n */\nexport function slashRouteLeaves(commands: Iterable<SlashCommandBuilder | ContextMenuCommandBuilder>): Set<string> {\n const leaves = new Set<string>();\n for (const command of commands) {\n if (!(command instanceof SlashCommandBuilder)) continue;\n for (const leaf of routeLeavesOf(command.toJSON())) leaves.add(leaf.route);\n }\n return leaves;\n}\n"],"mappings":";;;;;;AAUA,SAAgB,eAAe,OAAsC;CACjE,OAAO,wBAAwB,KAAK,KAAK,QAAQ,YAAY,oBAAoB,KAAK;AAC1F;;;;;;;;;;;ACAA,SAAgB,iBAAiB,UAAkF;CAC/G,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,WAAW,UAAU;EAC5B,IAAI,EAAE,mBAAmB,sBAAsB;EAC/C,KAAK,MAAM,QAAQ,cAAc,QAAQ,OAAO,CAAC,GAAG,OAAO,IAAI,KAAK,KAAK;CAC7E;CACA,OAAO;AACX"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { A as clearStore, M as contextMenuLeaves, j as guardedAccessor, k as accessorStore } from "./Bus-BVwmKc-A.mjs";
|
|
2
|
+
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
3
|
+
import { paint } from "@seedcord/errors";
|
|
4
|
+
import { Logger } from "@seedcord/logger";
|
|
5
|
+
import { routeLeavesOf } from "@seedcord/utils/internal";
|
|
6
|
+
import { ApplicationCommandType } from "discord-api-types/v10";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/CommandInjector.ts
|
|
9
|
+
const commandStorage = accessorStore();
|
|
10
|
+
const userMenuStorage = accessorStore();
|
|
11
|
+
const messageMenuStorage = accessorStore();
|
|
12
|
+
/**
|
|
13
|
+
* The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by
|
|
14
|
+
* {@link CommandInjector} after each deploy, and a read before that throws.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { Commands } from '@seedcord/gateway';
|
|
19
|
+
*
|
|
20
|
+
* await this.reply(`Try ${Commands['config/set'].mention}`);
|
|
21
|
+
*
|
|
22
|
+
* // a help listing
|
|
23
|
+
* const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
const Commands = guardedAccessor("Commands", commandStorage);
|
|
27
|
+
/**
|
|
28
|
+
* The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read
|
|
29
|
+
* before that throws.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { ContextMenus } from '@seedcord/gateway';
|
|
34
|
+
*
|
|
35
|
+
* const id = ContextMenus.user['View Profile'].id;
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
const ContextMenus = {
|
|
39
|
+
user: guardedAccessor("ContextMenus.user", userMenuStorage),
|
|
40
|
+
message: guardedAccessor("ContextMenus.message", messageMenuStorage)
|
|
41
|
+
};
|
|
42
|
+
const MULTI_GUILD = Symbol("seedcord:commands:multi-guild");
|
|
43
|
+
function commandKey(type, name) {
|
|
44
|
+
return `${type}:${name}`;
|
|
45
|
+
}
|
|
46
|
+
function slashBuildersOf(all) {
|
|
47
|
+
const slash = all.filter((command) => command instanceof SlashCommandBuilder);
|
|
48
|
+
return [...new Set(slash)];
|
|
49
|
+
}
|
|
50
|
+
/** @internal */
|
|
51
|
+
var CommandInjector = class {
|
|
52
|
+
logger = new Logger("Commands", { channel: "bot" });
|
|
53
|
+
inject(deploy, all) {
|
|
54
|
+
this.clear();
|
|
55
|
+
const ids = {
|
|
56
|
+
global: this.indexByKey(deploy.global),
|
|
57
|
+
guild: this.indexGuilds(deploy.guilds),
|
|
58
|
+
warned: /* @__PURE__ */ new Set()
|
|
59
|
+
};
|
|
60
|
+
let clickable = 0;
|
|
61
|
+
for (const command of slashBuildersOf(all)) {
|
|
62
|
+
const id = this.resolveId(ApplicationCommandType.ChatInput, command.name, ids, "falling back to plain text (not clickable)");
|
|
63
|
+
for (const leaf of routeLeavesOf(command.toJSON())) {
|
|
64
|
+
commandStorage[leaf.route] = {
|
|
65
|
+
mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),
|
|
66
|
+
...id && { id },
|
|
67
|
+
route: leaf.route,
|
|
68
|
+
description: leaf.description
|
|
69
|
+
};
|
|
70
|
+
if (id) clickable++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const menus = contextMenuLeaves(all);
|
|
74
|
+
this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);
|
|
75
|
+
this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);
|
|
76
|
+
this.logger.utils.summary("Linked commands", {
|
|
77
|
+
clickable,
|
|
78
|
+
plain: Object.keys(commandStorage).length - clickable,
|
|
79
|
+
"context menus": menus.user.size + menus.message.size
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
injectMenus(names, kind, storage, ids) {
|
|
83
|
+
for (const name of names) {
|
|
84
|
+
const id = this.resolveId(kind, name, ids, "leaving its id unset");
|
|
85
|
+
storage[name] = {
|
|
86
|
+
...id && { id },
|
|
87
|
+
name,
|
|
88
|
+
kind
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
indexByKey(collection) {
|
|
93
|
+
const map = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);
|
|
95
|
+
return map;
|
|
96
|
+
}
|
|
97
|
+
indexGuilds(guilds) {
|
|
98
|
+
const map = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const collection of guilds.values()) for (const command of collection.values()) {
|
|
100
|
+
const key = commandKey(command.type, command.name);
|
|
101
|
+
const existing = map.get(key);
|
|
102
|
+
if (existing === void 0) map.set(key, command.id);
|
|
103
|
+
else if (existing !== command.id) map.set(key, MULTI_GUILD);
|
|
104
|
+
}
|
|
105
|
+
return map;
|
|
106
|
+
}
|
|
107
|
+
resolveId(type, name, ids, consequence) {
|
|
108
|
+
const key = commandKey(type, name);
|
|
109
|
+
const global = ids.global.get(key);
|
|
110
|
+
if (global) return global;
|
|
111
|
+
const guild = ids.guild.get(key);
|
|
112
|
+
if (guild === void 0) return void 0;
|
|
113
|
+
if (guild === MULTI_GUILD) {
|
|
114
|
+
if (!ids.warned.has(key)) {
|
|
115
|
+
this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);
|
|
116
|
+
ids.warned.add(key);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
return guild;
|
|
121
|
+
}
|
|
122
|
+
toMention(route, id) {
|
|
123
|
+
return `</${route.replaceAll("/", " ")}:${id}>`;
|
|
124
|
+
}
|
|
125
|
+
toPlain(route) {
|
|
126
|
+
return `/${route.replaceAll("/", " ")}`;
|
|
127
|
+
}
|
|
128
|
+
clear() {
|
|
129
|
+
for (const store of [
|
|
130
|
+
commandStorage,
|
|
131
|
+
userMenuStorage,
|
|
132
|
+
messageMenuStorage
|
|
133
|
+
]) clearStore(store);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { Commands as n, ContextMenus as r, CommandInjector as t };
|
|
139
|
+
//# sourceMappingURL=subscribers-CGGQkUfM.mjs.map
|