lunibee 0.1.7 → 0.2.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 -21
- package/README.md +225 -223
- package/dist/builders/commands.d.ts +6 -4
- package/dist/builders/components.d.ts +23 -0
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +120 -24
- package/dist/builders/index.js.map +9 -8
- package/dist/collection/index.d.ts +68 -1
- package/dist/collection/index.js +335 -1
- package/dist/collection/index.js.map +4 -4
- package/dist/core/collector.d.ts +13 -0
- package/dist/core/events.d.ts +112 -2
- package/dist/core/index.d.ts +30 -93
- package/dist/core/index.js +3386 -957
- package/dist/core/index.js.map +48 -24
- package/dist/core/permissions.d.ts +48 -0
- package/dist/formatters/index.js.map +1 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3806 -1193
- package/dist/index.js.map +64 -43
- package/dist/managers/advanced.d.ts +116 -0
- package/dist/managers/base.d.ts +13 -2
- package/dist/managers/emoji.d.ts +1 -1
- package/dist/managers/guild-resources.d.ts +109 -0
- package/dist/managers/guild.d.ts +33 -2
- package/dist/managers/index.d.ts +44 -6
- package/dist/managers/index.js +1186 -150
- package/dist/managers/index.js.map +27 -21
- package/dist/managers/message.d.ts +10 -1
- package/dist/rest/decoder.d.ts +23 -0
- package/dist/rest/errors.d.ts +53 -0
- package/dist/rest/index.d.ts +21 -17
- package/dist/rest/index.js +526 -264
- package/dist/rest/index.js.map +14 -8
- package/dist/rest/limiter.d.ts +42 -0
- package/dist/rest/redis.d.ts +18 -1
- package/dist/rest/route.d.ts +31 -0
- package/dist/rest/routes.d.ts +22 -0
- package/dist/rest/scheduler.d.ts +42 -0
- package/dist/rest/store.d.ts +29 -0
- package/dist/rest/transport.d.ts +40 -0
- package/dist/sharding/bus.d.ts +31 -0
- package/dist/sharding/cluster.d.ts +10 -0
- package/dist/sharding/index.d.ts +39 -2
- package/dist/sharding/index.js +1105 -404
- package/dist/sharding/index.js.map +18 -8
- package/dist/structures/base.d.ts +10 -0
- package/dist/structures/channels.d.ts +56 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +305 -311
- package/dist/structures/index.js.map +18 -14
- package/dist/structures/interactions.d.ts +38 -52
- package/dist/structures/options.d.ts +55 -0
- package/dist/structures/resources.d.ts +3 -3
- package/dist/types/gateway-events.d.ts +132 -0
- package/dist/types/gateway.d.ts +197 -0
- package/dist/types/index.d.ts +53 -260
- package/dist/types/index.js +44 -2
- package/dist/types/index.js.map +5 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/voice/index.d.ts +32 -1
- package/dist/voice/index.js +73 -7
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/close-codes.d.ts +23 -0
- package/dist/ws/decoder.d.ts +58 -0
- package/dist/ws/heartbeat.d.ts +87 -0
- package/dist/ws/index.d.ts +18 -65
- package/dist/ws/index.js +911 -385
- package/dist/ws/index.js.map +15 -5
- package/dist/ws/opcodes.d.ts +14 -0
- package/dist/ws/protocol.d.ts +110 -0
- package/dist/ws/reconnect.d.ts +108 -0
- package/dist/ws/send-budget.d.ts +17 -0
- package/dist/ws/session.d.ts +100 -0
- package/dist/ws/state.d.ts +27 -0
- package/dist/ws/transport.d.ts +80 -0
- package/package.json +21 -20
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Gateway protocol: what a payload *means*.
|
|
3
|
+
*
|
|
4
|
+
* Pure translation from a received frame to an action, plus construction of
|
|
5
|
+
* the payloads Lunibee sends. It performs nothing: no socket, no timers, no
|
|
6
|
+
* session mutation, no event emission, no sending. The caller decides what to
|
|
7
|
+
* do with an action, which is what lets the whole protocol be tested without a
|
|
8
|
+
* connection.
|
|
9
|
+
*/
|
|
10
|
+
import type { GatewayPayload, GatewayPresence } from "@lunibee/types";
|
|
11
|
+
import type { ResumeInfo } from "./session.js";
|
|
12
|
+
/** Why a frame could not be understood. */
|
|
13
|
+
export type ProtocolViolation =
|
|
14
|
+
/** The frame was not valid JSON. */
|
|
15
|
+
"invalid-json"
|
|
16
|
+
/** The frame decoded to something other than a payload object. */
|
|
17
|
+
| "invalid-payload"
|
|
18
|
+
/** `HELLO` carried no usable heartbeat interval. */
|
|
19
|
+
| "invalid-hello";
|
|
20
|
+
/** What a received frame means. */
|
|
21
|
+
export type GatewayAction =
|
|
22
|
+
/** `op 10`: begin heartbeating and hand shake. */
|
|
23
|
+
{
|
|
24
|
+
type: "hello";
|
|
25
|
+
heartbeatInterval: number;
|
|
26
|
+
}
|
|
27
|
+
/** `op 0`: an event. `event` is null only if Discord omitted `t`. */
|
|
28
|
+
| {
|
|
29
|
+
type: "dispatch";
|
|
30
|
+
event: string | null;
|
|
31
|
+
data: unknown;
|
|
32
|
+
}
|
|
33
|
+
/** `op 1`: Discord asks for a heartbeat now. */
|
|
34
|
+
| {
|
|
35
|
+
type: "heartbeat";
|
|
36
|
+
}
|
|
37
|
+
/** `op 11`: the last heartbeat was acknowledged. */
|
|
38
|
+
| {
|
|
39
|
+
type: "heartbeat-ack";
|
|
40
|
+
data: unknown;
|
|
41
|
+
}
|
|
42
|
+
/** `op 7`: Discord asks the client to reconnect. */
|
|
43
|
+
| {
|
|
44
|
+
type: "reconnect";
|
|
45
|
+
}
|
|
46
|
+
/** `op 9`: the session is gone. `resumable` decides RESUME vs IDENTIFY. */
|
|
47
|
+
| {
|
|
48
|
+
type: "invalid-session";
|
|
49
|
+
resumable: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* An opcode this version does not handle.
|
|
53
|
+
*
|
|
54
|
+
* Ignored rather than treated as an error: Discord adds opcodes, and a
|
|
55
|
+
* client that fails on an unfamiliar one breaks itself on every addition.
|
|
56
|
+
*/
|
|
57
|
+
| {
|
|
58
|
+
type: "unknown";
|
|
59
|
+
opcode: number;
|
|
60
|
+
}
|
|
61
|
+
/** The frame was malformed. Never silently swallowed. */
|
|
62
|
+
| {
|
|
63
|
+
type: "invalid";
|
|
64
|
+
violation: ProtocolViolation;
|
|
65
|
+
message: string;
|
|
66
|
+
cause?: unknown;
|
|
67
|
+
};
|
|
68
|
+
/** A classified frame: its sequence, if any, and what it means. */
|
|
69
|
+
export interface ProtocolResult {
|
|
70
|
+
/**
|
|
71
|
+
* Sequence carried by the frame, or null when it carried none.
|
|
72
|
+
*
|
|
73
|
+
* Reported for every payload type, not just dispatches: recording it is
|
|
74
|
+
* the caller's decision, and the caller records before acting so a RESUME
|
|
75
|
+
* cannot be built from a sequence that was never seen.
|
|
76
|
+
*/
|
|
77
|
+
sequence: number | null;
|
|
78
|
+
/** What the frame means. */
|
|
79
|
+
action: GatewayAction;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Classifies one received frame.
|
|
83
|
+
*
|
|
84
|
+
* @param raw The frame text, already decompressed by the transport.
|
|
85
|
+
*/
|
|
86
|
+
export declare function classifyFrame(raw: string): ProtocolResult;
|
|
87
|
+
/** Classifies an already-parsed payload. */
|
|
88
|
+
export declare function classifyPayload(payload: unknown): ProtocolResult;
|
|
89
|
+
/** Identification properties sent with IDENTIFY. */
|
|
90
|
+
export interface IdentifyProperties {
|
|
91
|
+
os?: string;
|
|
92
|
+
browser?: string;
|
|
93
|
+
device?: string;
|
|
94
|
+
[key: string]: unknown;
|
|
95
|
+
}
|
|
96
|
+
/** Everything IDENTIFY needs. */
|
|
97
|
+
export interface IdentifyOptions {
|
|
98
|
+
token: string;
|
|
99
|
+
intents: number;
|
|
100
|
+
shardId: number;
|
|
101
|
+
shardCount: number;
|
|
102
|
+
properties?: IdentifyProperties;
|
|
103
|
+
presence?: GatewayPresence;
|
|
104
|
+
}
|
|
105
|
+
/** Builds an IDENTIFY payload. */
|
|
106
|
+
export declare function identifyPayload(options: IdentifyOptions): GatewayPayload;
|
|
107
|
+
/** Builds a RESUME payload from the session's resume material. */
|
|
108
|
+
export declare function resumePayload(token: string, resume: ResumeInfo): GatewayPayload;
|
|
109
|
+
/** Builds a heartbeat payload carrying the last sequence seen. */
|
|
110
|
+
export declare function heartbeatPayload(sequence: number | null): GatewayPayload;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** What a closed connection should do next. */
|
|
2
|
+
export type CloseAction =
|
|
3
|
+
/** Reconnect and RESUME the existing session. */
|
|
4
|
+
"resume"
|
|
5
|
+
/** Reconnect, but IDENTIFY afresh: the session cannot be resumed. */
|
|
6
|
+
| "identify"
|
|
7
|
+
/** Do not reconnect; the condition will not fix itself. */
|
|
8
|
+
| "stop";
|
|
9
|
+
/**
|
|
10
|
+
* Close codes that no reconnect can recover from: bad token, bad shard
|
|
11
|
+
* configuration, bad API version, or intents the application is not approved
|
|
12
|
+
* for. Retrying these hammers Discord with a request that cannot succeed.
|
|
13
|
+
*/
|
|
14
|
+
export declare const FATAL_CLOSE_CODES: readonly number[];
|
|
15
|
+
/**
|
|
16
|
+
* Close codes that keep the connection recoverable but destroy the session, so
|
|
17
|
+
* the next connection must IDENTIFY rather than RESUME.
|
|
18
|
+
*/
|
|
19
|
+
export declare const IDENTIFY_CLOSE_CODES: readonly number[];
|
|
20
|
+
/**
|
|
21
|
+
* Classifies a close code.
|
|
22
|
+
*
|
|
23
|
+
* Any code outside the two tables above is **reconnectable**: transport-level
|
|
24
|
+
* closes (`1006`), server restarts (`1001`), Discord's own `4000`, and the
|
|
25
|
+
* `1000` the Gateway itself sends after a non-resumable `op 9` all belong here.
|
|
26
|
+
* Whether that reconnect resumes or identifies is not a property of the code —
|
|
27
|
+
* it is whether a session survives, which is why `canResume` is a parameter
|
|
28
|
+
* rather than something this module works out for itself.
|
|
29
|
+
*
|
|
30
|
+
* @param code WebSocket close code.
|
|
31
|
+
* @param canResume Whether the session can still be resumed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function classifyCloseCode(code: number, canResume: boolean): CloseAction;
|
|
34
|
+
/** Why a reconnect was not scheduled. */
|
|
35
|
+
export type ScheduleRefusal =
|
|
36
|
+
/** Automatic reconnect is disabled for this Gateway. */
|
|
37
|
+
"disabled"
|
|
38
|
+
/** A reconnect is already armed; a second would open a second socket. */
|
|
39
|
+
| "pending"
|
|
40
|
+
/** The attempt budget is spent. */
|
|
41
|
+
| "exhausted";
|
|
42
|
+
/** Outcome of asking for a reconnect. */
|
|
43
|
+
export type ScheduleResult = {
|
|
44
|
+
scheduled: true;
|
|
45
|
+
delayMs: number;
|
|
46
|
+
attempt: number;
|
|
47
|
+
} | {
|
|
48
|
+
scheduled: false;
|
|
49
|
+
reason: ScheduleRefusal;
|
|
50
|
+
};
|
|
51
|
+
/** Configuration for {@link GatewayReconnect}. */
|
|
52
|
+
export interface ReconnectOptions {
|
|
53
|
+
/** Whether automatic reconnect is permitted at all. */
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
/** Maximum consecutive attempts before giving up. */
|
|
56
|
+
maxAttempts: number;
|
|
57
|
+
/** Delay for the first attempt, doubled per attempt. */
|
|
58
|
+
baseDelay: number;
|
|
59
|
+
/** Ceiling for the backoff. */
|
|
60
|
+
maxDelay: number;
|
|
61
|
+
/** Randomness source for jitter; injected in tests. */
|
|
62
|
+
random?: () => number;
|
|
63
|
+
}
|
|
64
|
+
/** Reconnect scheduling, backoff and attempt coordination. */
|
|
65
|
+
export declare class GatewayReconnect {
|
|
66
|
+
#private;
|
|
67
|
+
constructor(options: ReconnectOptions);
|
|
68
|
+
/** Whether automatic reconnect is permitted. */
|
|
69
|
+
get enabled(): boolean;
|
|
70
|
+
/** Consecutive attempts made since the last {@link reset}. */
|
|
71
|
+
get attempts(): number;
|
|
72
|
+
/** Whether a reconnect timer is currently armed. */
|
|
73
|
+
get pending(): boolean;
|
|
74
|
+
/** Whether the attempt budget is spent. */
|
|
75
|
+
get exhausted(): boolean;
|
|
76
|
+
/** Whether a connection attempt is currently in flight. */
|
|
77
|
+
get connecting(): boolean;
|
|
78
|
+
/** The in-flight attempt, or undefined when none is active. */
|
|
79
|
+
get attemptPromise(): Promise<void> | undefined;
|
|
80
|
+
/** Classifies a close code against this Gateway's policy. */
|
|
81
|
+
classifyClose(code: number, canResume: boolean): CloseAction;
|
|
82
|
+
/** Whether another reconnect is currently allowed. */
|
|
83
|
+
canReconnect(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Arms a reconnect, unless one is already armed or no attempt is allowed.
|
|
86
|
+
*
|
|
87
|
+
* Refusing while `pending` is what keeps repeated closes from stacking
|
|
88
|
+
* timers: several closes in a row produce one reconnect, not several
|
|
89
|
+
* competing sockets.
|
|
90
|
+
*/
|
|
91
|
+
schedule(run: () => void): ScheduleResult;
|
|
92
|
+
/** Cancels an armed reconnect. Safe to call when none is armed. */
|
|
93
|
+
cancel(): void;
|
|
94
|
+
/** Clears the attempt counter after a connection succeeds. */
|
|
95
|
+
reset(): void;
|
|
96
|
+
/**
|
|
97
|
+
* Coordinates one logical connection attempt.
|
|
98
|
+
*
|
|
99
|
+
* Callers that ask while an attempt is in flight join it instead of
|
|
100
|
+
* starting a second: three `connect()` calls produce one socket attempt and
|
|
101
|
+
* one shared promise.
|
|
102
|
+
*
|
|
103
|
+
* @param start Invoked exactly once, when this call begins a new attempt.
|
|
104
|
+
*/
|
|
105
|
+
attempt(start: () => void): Promise<void>;
|
|
106
|
+
/** Settles the in-flight attempt, resolving it or rejecting with `error`. */
|
|
107
|
+
settle(error?: Error): void;
|
|
108
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window budget for outgoing Gateway payloads.
|
|
3
|
+
*
|
|
4
|
+
* Discord allows 120 sends per 60 seconds. Application traffic is capped at
|
|
5
|
+
* 115 so the remaining headroom is reserved for privileged sends (heartbeats),
|
|
6
|
+
* which are still recorded so the true total stays under Discord's limit.
|
|
7
|
+
*/
|
|
8
|
+
export declare class SendBudget {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(limit?: number, windowMs?: number);
|
|
11
|
+
/** Sends recorded within the current window. */
|
|
12
|
+
get used(): number;
|
|
13
|
+
/** Whether a send may go out now. Privileged sends always may. */
|
|
14
|
+
allows(privileged: boolean, now?: number): boolean;
|
|
15
|
+
/** Records a send that went out. */
|
|
16
|
+
record(now?: number): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity of a Discord Gateway session, and the decision of how to open the
|
|
3
|
+
* next connection with it.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately owns nothing else: no socket, no timers, no opcode dispatch, no
|
|
6
|
+
* compression, no event emission. Those belong to the transport, heartbeat,
|
|
7
|
+
* reconnect and protocol seams. Keeping this object small is the point —
|
|
8
|
+
* session confusion is what produced both WS-001 and WS-005.
|
|
9
|
+
*/
|
|
10
|
+
/** Everything needed to send a RESUME, or nothing when one is impossible. */
|
|
11
|
+
export interface ResumeInfo {
|
|
12
|
+
/** Discord's session identifier from READY. */ sessionId: string;
|
|
13
|
+
/** Last sequence number observed on this session. */ sequence: number;
|
|
14
|
+
/** Host READY nominated for resuming this session. */ resumeURL: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* What the next connection must do.
|
|
18
|
+
*
|
|
19
|
+
* A discriminated union rather than a boolean plus three nullable fields: the
|
|
20
|
+
* caller cannot reach for a session id on the identify branch, so "resume with
|
|
21
|
+
* an undefined session" is not a state this codebase can express.
|
|
22
|
+
*/
|
|
23
|
+
export type HandshakeIntent = ({
|
|
24
|
+
type: "resume";
|
|
25
|
+
} & ResumeInfo) | {
|
|
26
|
+
type: "identify";
|
|
27
|
+
};
|
|
28
|
+
/** Why a session was discarded. */
|
|
29
|
+
export type InvalidationReason =
|
|
30
|
+
/** Discord sent `op 9` with `d: false`. */
|
|
31
|
+
"invalid-session"
|
|
32
|
+
/** The connection closed with a code that forbids resuming. */
|
|
33
|
+
| "session-timeout"
|
|
34
|
+
/** Discarded by the owner without a protocol cause. */
|
|
35
|
+
| "explicit";
|
|
36
|
+
/**
|
|
37
|
+
* Session state for one Gateway shard.
|
|
38
|
+
*
|
|
39
|
+
* **Generations.** Every connection attempt takes a token from
|
|
40
|
+
* {@link GatewaySession.beginConnection}, and every mutation must present it.
|
|
41
|
+
* A socket that has been superseded still holds the old token, so a late
|
|
42
|
+
* dispatch arriving on it cannot advance the new connection's sequence or
|
|
43
|
+
* overwrite its session id. Ownership is therefore structural rather than a
|
|
44
|
+
* chain of `if (this.#ws === ws)` checks at each call site.
|
|
45
|
+
*/
|
|
46
|
+
export declare class GatewaySession {
|
|
47
|
+
#private;
|
|
48
|
+
/** Why the last session was discarded, for diagnostics and reconnect policy. */
|
|
49
|
+
get lastInvalidation(): InvalidationReason | undefined;
|
|
50
|
+
/** Token of the current connection attempt; mutations must match it. */
|
|
51
|
+
get generation(): number;
|
|
52
|
+
/** Discord's session id, or undefined when no session is established. */
|
|
53
|
+
get sessionId(): string | undefined;
|
|
54
|
+
/** Host to reconnect to for a RESUME, or undefined when none is known. */
|
|
55
|
+
get resumeURL(): string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Last sequence number seen, or null when none has been observed.
|
|
58
|
+
*
|
|
59
|
+
* Null, never zero, marks "nothing seen": sequence `0` is a legitimate
|
|
60
|
+
* value that must survive into a RESUME.
|
|
61
|
+
*/
|
|
62
|
+
get sequence(): number | null;
|
|
63
|
+
/** Whether a RESUME is possible: a session, a resume host, and a sequence. */
|
|
64
|
+
get canResume(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Opens a new connection generation and returns its token.
|
|
67
|
+
*
|
|
68
|
+
* The session itself is untouched: reconnecting to resume must keep the
|
|
69
|
+
* session id and sequence. Only ownership moves.
|
|
70
|
+
*/
|
|
71
|
+
beginConnection(): number;
|
|
72
|
+
/** Whether `token` still owns this session. */
|
|
73
|
+
owns(token: number): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Records a sequence number from a payload.
|
|
76
|
+
* @returns Whether the token owned the session and the value was stored.
|
|
77
|
+
*/
|
|
78
|
+
recordSequence(sequence: number, token: number): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Establishes the session from a READY payload.
|
|
81
|
+
* @returns Whether the token owned the session and READY was well-formed.
|
|
82
|
+
*/
|
|
83
|
+
activate(payload: unknown, token: number): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Discards the session so the next connection must IDENTIFY.
|
|
86
|
+
*
|
|
87
|
+
* Resumable invalidation (`op 9` with `d: true`) is *not* an invalidation:
|
|
88
|
+
* the session survives and the caller simply reconnects, so this is only
|
|
89
|
+
* called for the non-resumable cases.
|
|
90
|
+
*
|
|
91
|
+
* @returns Whether the token owned the session and state was discarded.
|
|
92
|
+
*/
|
|
93
|
+
invalidate(token: number, reason?: InvalidationReason): boolean;
|
|
94
|
+
/** Resume material, or undefined when a RESUME is not possible. */
|
|
95
|
+
resumeInfo(): ResumeInfo | undefined;
|
|
96
|
+
/** What the next connection must send: RESUME with material, or IDENTIFY. */
|
|
97
|
+
handshake(): HandshakeIntent;
|
|
98
|
+
/** Host the next connection should dial: the resume host, else `fallback`. */
|
|
99
|
+
connectURL(fallback: string): string;
|
|
100
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Gateway connection lifecycle states. */
|
|
2
|
+
export declare enum GatewayState {
|
|
3
|
+
/** Initial connection state. */ Connect = "CONNECT",
|
|
4
|
+
/** Gateway HELLO received state. */ Hello = "HELLO",
|
|
5
|
+
/** IDENTIFY operation in progress. */ Identify = "IDENTIFY",
|
|
6
|
+
/** RESUME operation in progress. */ Resume = "RESUME",
|
|
7
|
+
/** Gateway READY state. */ Ready = "READY",
|
|
8
|
+
/** Gateway dispatch processing state. */ Dispatch = "DISPATCH",
|
|
9
|
+
/** Heartbeat processing state. */ Heartbeat = "HEARTBEAT",
|
|
10
|
+
/** Reconnect in progress. */ Reconnect = "RECONNECT",
|
|
11
|
+
/** Gateway is permanently closed. */ Closed = "CLOSED"
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Discord.js-familiar alias for {@link GatewayState}.
|
|
15
|
+
*
|
|
16
|
+
* Discord.js exposes connection status via a `Status` enum. Lunibee's canonical
|
|
17
|
+
* name is {@link GatewayState}; this is an additive alias so `discord.js` users
|
|
18
|
+
* find the expected name. Note the *values* remain Lunibee's string states
|
|
19
|
+
* (e.g. `"READY"`), not Discord.js's numeric `Status` members — an intentional
|
|
20
|
+
* divergence documented in the compatibility matrix.
|
|
21
|
+
*/
|
|
22
|
+
export { GatewayState as Status };
|
|
23
|
+
/** Gateway protocol error. */
|
|
24
|
+
export declare class GatewayError extends Error {
|
|
25
|
+
/** Gateway close/error code. */ readonly code?: number;
|
|
26
|
+
/** Creates a Gateway error. @param message Error message. @param code Optional Gateway code. @param options Optional error metadata. */ constructor(message: string, code?: number, options?: ErrorOptions);
|
|
27
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebSocket a Gateway connection runs on.
|
|
3
|
+
*
|
|
4
|
+
* Owns socket construction, listener wiring, sending, closing, and replacement.
|
|
5
|
+
* It is infrastructure, not Discord semantics: it never inspects an opcode, a
|
|
6
|
+
* session, or a heartbeat. Frames go out as text and come back as text.
|
|
7
|
+
*/
|
|
8
|
+
import { type GatewayDecoder } from "./decoder.js";
|
|
9
|
+
/** Creates the underlying socket. Injected to support alternative runtimes. */
|
|
10
|
+
export type SocketFactory = (url: string) => WebSocket;
|
|
11
|
+
/** Callbacks a transport reports to its owner. */
|
|
12
|
+
export interface TransportHandlers {
|
|
13
|
+
/** The socket opened and is ready to send. */
|
|
14
|
+
onOpen: () => void;
|
|
15
|
+
/** A complete frame arrived, already decompressed. */
|
|
16
|
+
onFrame: (raw: string) => void;
|
|
17
|
+
/** The socket closed. */
|
|
18
|
+
onClose: (code: number, reason: string) => void;
|
|
19
|
+
/** A socket or decoding failure. Not a close on its own. */
|
|
20
|
+
onError: (error: Error) => void;
|
|
21
|
+
}
|
|
22
|
+
/** Outcome of opening a connection. */
|
|
23
|
+
export type ConnectResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
error: Error;
|
|
28
|
+
};
|
|
29
|
+
/** Configuration for {@link WebSocketTransport}. */
|
|
30
|
+
export interface TransportOptions {
|
|
31
|
+
/** Handlers for this transport's lifetime. */
|
|
32
|
+
handlers: TransportHandlers;
|
|
33
|
+
/** Frame decoder. Defaults to plain text, or zlib-stream when compressing. */
|
|
34
|
+
decoder?: GatewayDecoder;
|
|
35
|
+
/** Whether the connection negotiates `zlib-stream` compression. */
|
|
36
|
+
compress?: boolean;
|
|
37
|
+
/** Socket constructor. Defaults to the ambient `WebSocket`. */
|
|
38
|
+
createSocket?: SocketFactory;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A replaceable WebSocket connection.
|
|
42
|
+
*
|
|
43
|
+
* **Generations.** Each {@link connect} takes a generation, and every event is
|
|
44
|
+
* checked against the current one. A socket that has been replaced still holds
|
|
45
|
+
* its listeners and can still fire — especially a frame that finishes
|
|
46
|
+
* decompressing after the swap — and every such event is dropped. The owner
|
|
47
|
+
* therefore never has to ask "is this still my socket?".
|
|
48
|
+
*/
|
|
49
|
+
export declare class WebSocketTransport {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(options: TransportOptions);
|
|
52
|
+
/** Whether a socket is open and able to send. */
|
|
53
|
+
get connected(): boolean;
|
|
54
|
+
/** Whether a socket is connecting or open, and therefore still in play. */
|
|
55
|
+
get live(): boolean;
|
|
56
|
+
/** Current connection generation; increments on every {@link connect}. */
|
|
57
|
+
get generation(): number;
|
|
58
|
+
/**
|
|
59
|
+
* Opens a connection, replacing any existing one.
|
|
60
|
+
*
|
|
61
|
+
* A construction failure is returned rather than reported through
|
|
62
|
+
* `onError`: the caller is mid-`connect` and must decide what the failure
|
|
63
|
+
* means for the attempt it is running.
|
|
64
|
+
*/
|
|
65
|
+
connect(url: string): ConnectResult;
|
|
66
|
+
/**
|
|
67
|
+
* Writes a frame.
|
|
68
|
+
* @returns Whether it reached the socket.
|
|
69
|
+
*/
|
|
70
|
+
send(data: string): boolean;
|
|
71
|
+
/** Closes the current socket, if any. Its `close` event still fires. */
|
|
72
|
+
close(code?: number, reason?: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Abandons the current socket without reporting its close.
|
|
75
|
+
*
|
|
76
|
+
* Used for a deliberate shutdown, where the owner has already decided the
|
|
77
|
+
* connection is over and must not be woken by the resulting close event.
|
|
78
|
+
*/
|
|
79
|
+
destroy(code?: number, reason?: string): void;
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lunibee",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A lightweight, Bun-first Discord API library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -88,37 +88,38 @@
|
|
|
88
88
|
"scripts": {
|
|
89
89
|
"build": "bun scripts/build.ts",
|
|
90
90
|
"build:dts": "bun scripts/build-dts.ts",
|
|
91
|
-
"format": "
|
|
91
|
+
"format": "prettier --write \"packages/*/src/**/*.ts\" \"tests/**/*.ts\"",
|
|
92
|
+
"format:check": "prettier --check \"packages/*/src/**/*.ts\" \"tests/**/*.ts\"",
|
|
92
93
|
"check:deps": "bun scripts/check-dependency-graph.ts",
|
|
94
|
+
"audit:api": "bun scripts/api-audit.ts",
|
|
93
95
|
"typecheck": "bunx tsc --noEmit --project tsconfig.json",
|
|
94
96
|
"ci:install": "bun install --frozen-lockfile",
|
|
95
97
|
"ci:deps": "bun run check:deps",
|
|
96
98
|
"ci:typecheck": "bun run typecheck",
|
|
97
|
-
"ci:test": "bun test --coverage",
|
|
98
|
-
"ci": "bun run ci:deps && bun run ci:typecheck && bun run ci:test",
|
|
99
|
-
"bench": "bun benchmarks/
|
|
100
|
-
"bench:compare": "bun benchmarks/compare.ts",
|
|
101
|
-
"bench:stress": "bun benchmarks/stress.ts",
|
|
99
|
+
"ci:test": "bun test --coverage && bun test ./packages/testing/src",
|
|
100
|
+
"ci": "bun run ci:deps && bun run audit:api && bun run format:check && bun run ci:typecheck && bun run ci:test",
|
|
101
|
+
"bench": "bun benchmarks/parity.ts",
|
|
102
102
|
"build:all": "bun run build && bun run build:dts",
|
|
103
103
|
"publish:all": "bun run scripts/publish.ts"
|
|
104
104
|
},
|
|
105
105
|
"dependencies": {
|
|
106
|
-
"@lunibee/builders": "^0.
|
|
107
|
-
"@lunibee/collection": "^0.
|
|
108
|
-
"@lunibee/core": "^0.
|
|
109
|
-
"@lunibee/formatters": "^0.
|
|
110
|
-
"@lunibee/handlers": "^0.
|
|
111
|
-
"@lunibee/managers": "^0.
|
|
112
|
-
"@lunibee/rest": "^0.
|
|
113
|
-
"@lunibee/sharding": "^0.
|
|
114
|
-
"@lunibee/structures": "^0.
|
|
115
|
-
"@lunibee/types": "^0.
|
|
116
|
-
"@lunibee/utils": "^0.
|
|
117
|
-
"@lunibee/voice": "^0.
|
|
118
|
-
"@lunibee/ws": "^0.
|
|
106
|
+
"@lunibee/builders": "^0.2.0",
|
|
107
|
+
"@lunibee/collection": "^0.2.0",
|
|
108
|
+
"@lunibee/core": "^0.2.0",
|
|
109
|
+
"@lunibee/formatters": "^0.2.0",
|
|
110
|
+
"@lunibee/handlers": "^0.2.0",
|
|
111
|
+
"@lunibee/managers": "^0.2.0",
|
|
112
|
+
"@lunibee/rest": "^0.2.0",
|
|
113
|
+
"@lunibee/sharding": "^0.2.0",
|
|
114
|
+
"@lunibee/structures": "^0.2.0",
|
|
115
|
+
"@lunibee/types": "^0.2.0",
|
|
116
|
+
"@lunibee/utils": "^0.2.0",
|
|
117
|
+
"@lunibee/voice": "^0.2.0",
|
|
118
|
+
"@lunibee/ws": "^0.2.0"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@types/bun": "^1.4.1",
|
|
122
|
+
"prettier": "^3.9.9",
|
|
122
123
|
"typescript": "^7.0.2"
|
|
123
124
|
},
|
|
124
125
|
"engines": {
|