lunibee 0.1.8 → 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.
Files changed (44) hide show
  1. package/README.md +2 -0
  2. package/dist/builders/components.d.ts +5 -0
  3. package/dist/builders/index.js +72 -12
  4. package/dist/builders/index.js.map +5 -5
  5. package/dist/collection/index.d.ts +68 -1
  6. package/dist/collection/index.js +334 -1
  7. package/dist/collection/index.js.map +3 -3
  8. package/dist/core/collector.d.ts +13 -0
  9. package/dist/core/events.d.ts +29 -3
  10. package/dist/core/index.d.ts +22 -8
  11. package/dist/core/index.js +1526 -124
  12. package/dist/core/index.js.map +26 -22
  13. package/dist/core/permissions.d.ts +48 -0
  14. package/dist/index.js +1578 -268
  15. package/dist/index.js.map +30 -29
  16. package/dist/managers/advanced.d.ts +116 -0
  17. package/dist/managers/base.d.ts +13 -2
  18. package/dist/managers/emoji.d.ts +1 -1
  19. package/dist/managers/guild.d.ts +28 -2
  20. package/dist/managers/index.d.ts +37 -5
  21. package/dist/managers/index.js +870 -152
  22. package/dist/managers/index.js.map +18 -16
  23. package/dist/managers/message.d.ts +10 -1
  24. package/dist/rest/errors.d.ts +33 -0
  25. package/dist/rest/index.d.ts +1 -1
  26. package/dist/rest/index.js +52 -6
  27. package/dist/rest/index.js.map +5 -5
  28. package/dist/rest/routes.d.ts +20 -0
  29. package/dist/sharding/index.d.ts +29 -0
  30. package/dist/sharding/index.js +99 -15
  31. package/dist/sharding/index.js.map +8 -8
  32. package/dist/structures/base.d.ts +2 -0
  33. package/dist/structures/index.js +96 -42
  34. package/dist/structures/index.js.map +10 -9
  35. package/dist/structures/interactions.d.ts +26 -0
  36. package/dist/types/gateway.d.ts +16 -0
  37. package/dist/types/index.d.ts +55 -1
  38. package/dist/types/index.js.map +2 -2
  39. package/dist/voice/index.d.ts +3 -0
  40. package/dist/voice/index.js +15 -1
  41. package/dist/voice/index.js.map +3 -3
  42. package/dist/ws/index.js +13 -5
  43. package/dist/ws/index.js.map +6 -6
  44. package/package.json +14 -14
@@ -1,4 +1,12 @@
1
1
  import { Collection } from "@lunibee/collection";
2
+ /** Opt-in per-channel message cache. Messages are temporary data, so they
3
+ * use bounded TTL/LRU storage rather than permanent resource storage. */
4
+ export interface MessageCacheOptions {
5
+ /** Messages kept per channel; least-recently-used are evicted. Default 100. */
6
+ maxSize?: number;
7
+ /** Sliding TTL in ms. Omit for no expiry. */
8
+ ttl?: number;
9
+ }
2
10
  import { REST } from "@lunibee/rest";
3
11
  import { Message, type ResourceContext } from "@lunibee/structures";
4
12
  export type MessageCreateOptions = Record<string, unknown> & {
@@ -9,8 +17,9 @@ export type MessageEditOptions = Record<string, unknown> & {
9
17
  };
10
18
  export declare class MessageManager {
11
19
  #private;
20
+ /** Cached messages; always empty unless a message cache is configured. */
12
21
  readonly cache: Collection<string, Message>;
13
- constructor(rest: REST, context: ResourceContext, channelId: string);
22
+ constructor(rest: REST, context: ResourceContext, channelId: string, cache?: MessageCacheOptions);
14
23
  resolve(messageId: string): Promise<Message>;
15
24
  send(options: MessageCreateOptions): Promise<Message>;
16
25
  fetch(messageId: string): Promise<Message>;
@@ -1,5 +1,34 @@
1
+ /** Why a REST request failed, for handling without matching on status codes. */
2
+ export type RESTErrorKind =
3
+ /** Cancelled through an AbortSignal. */
4
+ "aborted"
5
+ /** The attempt exceeded the transport timeout. */
6
+ | "timeout"
7
+ /** No response: DNS, TLS, connection reset. */
8
+ | "network"
9
+ /** 429 that outlived the retry budget. */
10
+ | "rateLimited"
11
+ /** 401: invalid or revoked token. */
12
+ | "authentication"
13
+ /** 403: missing access or permissions. */
14
+ | "permission"
15
+ /** 404: unknown resource. */
16
+ | "notFound"
17
+ /** 400 / 422: the request body or parameters were rejected. */
18
+ | "validation"
19
+ /** 5xx: Discord failed to handle the request. */
20
+ | "server"
21
+ /** Any other 4xx. */
22
+ | "client";
23
+ /**
24
+ * Replaces the token segment of webhook and interaction paths with `:token`,
25
+ * so request paths can appear in errors, hooks and logs without leaking a
26
+ * credential.
27
+ */
28
+ export declare function redactPath(path: string): string;
1
29
  /** Error thrown when Discord rejects a REST request. */
2
30
  export declare class RESTError extends Error {
31
+ /** Failure category. */ readonly kind: RESTErrorKind;
3
32
  /** HTTP status returned by Discord. */ readonly status: number;
4
33
  /** Discord API error code, when provided. */ readonly code?: number;
5
34
  /** Raw Discord validation/error payload. */ readonly errors?: unknown;
@@ -10,7 +39,11 @@ export declare class RESTError extends Error {
10
39
  method?: string;
11
40
  path?: string;
12
41
  cause?: unknown;
42
+ kind?: RESTErrorKind;
13
43
  });
44
+ /** Whether sending the same request again later could succeed. Validation,
45
+ * permission, authentication and not-found failures never will. */
46
+ get retryable(): boolean;
14
47
  }
15
48
  /** Creates the cancellation error shared by every stage of the request pipeline. */
16
49
  export declare function abortError(path: string, reason: unknown): RESTError;
@@ -160,7 +160,7 @@ export declare class REST {
160
160
  patchWithFiles<T>(path: string, payload: unknown, files: RESTFileAttachment[], options?: RESTRequestOptions): Promise<T>;
161
161
  }
162
162
  export { Routes } from "./routes.js";
163
- export { RESTError, abortError } from "./errors.js";
163
+ export { RESTError, abortError, redactPath, type RESTErrorKind, } from "./errors.js";
164
164
  export { createRouteKey, normalizeRoutePath, majorParameter, scopeBucket, serializeQuery, type RouteKey, type RESTQuery, } from "./route.js";
165
165
  export { RequestScheduler } from "./scheduler.js";
166
166
  export { RateLimiter } from "./limiter.js";
@@ -2,7 +2,7 @@
2
2
  // packages/rest/package.json
3
3
  var package_default = {
4
4
  name: "@lunibee/rest",
5
- version: "0.1.8",
5
+ version: "0.2.0",
6
6
  type: "module",
7
7
  module: "./src/index.ts",
8
8
  types: "./src/index.ts",
@@ -62,7 +62,29 @@ class MemoryRateLimitStore {
62
62
  }
63
63
 
64
64
  // packages/rest/src/errors.ts
65
+ function kindFromStatus(status) {
66
+ if (status === 429)
67
+ return "rateLimited";
68
+ if (status === 401)
69
+ return "authentication";
70
+ if (status === 403)
71
+ return "permission";
72
+ if (status === 404)
73
+ return "notFound";
74
+ if (status === 400 || status === 422)
75
+ return "validation";
76
+ if (status >= 500)
77
+ return "server";
78
+ if (status === 0)
79
+ return "network";
80
+ return "client";
81
+ }
82
+ function redactPath(path) {
83
+ return path.replace(/^(\/webhooks\/[^/?]+\/)[^/?]+/, "$1:token").replace(/^(\/interactions\/[^/?]+\/)[^/?]+/, "$1:token");
84
+ }
85
+
65
86
  class RESTError extends Error {
87
+ kind;
66
88
  status;
67
89
  code;
68
90
  errors;
@@ -72,14 +94,18 @@ class RESTError extends Error {
72
94
  super(message, options.cause === undefined ? undefined : { cause: options.cause });
73
95
  this.name = "RESTError";
74
96
  this.status = status;
97
+ this.kind = options.kind ?? kindFromStatus(status);
75
98
  this.code = code;
76
99
  this.errors = errors;
77
100
  this.method = options.method;
78
- this.path = options.path;
101
+ this.path = options.path === undefined ? undefined : redactPath(options.path);
102
+ }
103
+ get retryable() {
104
+ return this.kind === "rateLimited" || this.kind === "server" || this.kind === "network" || this.kind === "timeout";
79
105
  }
80
106
  }
81
107
  function abortError(path, reason) {
82
- return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
108
+ return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason, kind: "aborted" });
83
109
  }
84
110
  async function sleep(delay, signal, path) {
85
111
  if (delay <= 0)
@@ -414,8 +440,23 @@ var Routes = {
414
440
  invite: (code) => `/invites/${encodeURIComponent(code)}`,
415
441
  guildVanity: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/vanity-url`,
416
442
  guildEmoji: (guildId, emojiId) => emojiId ? `/guilds/${snowflake(guildId, "Guild ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/emojis`,
417
- applicationEmoji: (applicationId, emojiId) => emojiId ? `/applications/${snowflake(applicationId, "Application ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/emojis`
443
+ applicationEmoji: (applicationId, emojiId) => emojiId ? `/applications/${snowflake(applicationId, "Application ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/emojis`,
444
+ pollExpire: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/polls/${snowflake(messageId, "Message ID")}/expire`,
445
+ pollAnswerVoters: (channelId, messageId, answerId) => `/channels/${snowflake(channelId, "Channel ID")}/polls/${snowflake(messageId, "Message ID")}/answers/${integer(answerId, "Answer ID")}`,
446
+ guildSticker: (guildId, stickerId) => stickerId ? `/guilds/${snowflake(guildId, "Guild ID")}/stickers/${snowflake(stickerId, "Sticker ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/stickers`,
447
+ sendSoundboardSound: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/send-soundboard-sound`,
448
+ soundboardDefaultSounds: () => "/soundboard-default-sounds",
449
+ guildSoundboardSound: (guildId, soundId) => soundId ? `/guilds/${snowflake(guildId, "Guild ID")}/soundboard-sounds/${snowflake(soundId, "Sound ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/soundboard-sounds`,
450
+ applicationSkus: (applicationId) => `/applications/${snowflake(applicationId, "Application ID")}/skus`,
451
+ applicationEntitlement: (applicationId, entitlementId) => entitlementId ? `/applications/${snowflake(applicationId, "Application ID")}/entitlements/${snowflake(entitlementId, "Entitlement ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/entitlements`,
452
+ consumeEntitlement: (applicationId, entitlementId) => `/applications/${snowflake(applicationId, "Application ID")}/entitlements/${snowflake(entitlementId, "Entitlement ID")}/consume`,
453
+ skuSubscription: (skuId, subscriptionId) => subscriptionId ? `/skus/${snowflake(skuId, "SKU ID")}/subscriptions/${snowflake(subscriptionId, "Subscription ID")}` : `/skus/${snowflake(skuId, "SKU ID")}/subscriptions`
418
454
  };
455
+ function integer(value, field) {
456
+ if (!Number.isInteger(value) || value < 0)
457
+ throw new TypeError(`${field} must be a non-negative integer.`);
458
+ return String(value);
459
+ }
419
460
  function snowflake(value, field) {
420
461
  if (!/^\d{1,20}$/.test(value))
421
462
  throw new TypeError(`${field} must be a valid Discord snowflake.`);
@@ -729,7 +770,8 @@ class REST {
729
770
  throw new RESTError(failure.message, 0, undefined, undefined, {
730
771
  method,
731
772
  path,
732
- cause: failure.cause ?? failure
773
+ cause: failure.cause ?? failure,
774
+ kind: failure.timedOut ? "timeout" : "network"
733
775
  });
734
776
  }
735
777
  this.#emit(this.#hooks.onResponse, {
@@ -828,6 +870,9 @@ class REST {
828
870
  #emit(hook, ctx) {
829
871
  if (!hook)
830
872
  return;
873
+ const withPath = ctx;
874
+ if (typeof withPath.path === "string")
875
+ ctx = { ...ctx, path: redactPath(withPath.path) };
831
876
  Promise.resolve().then(() => hook(ctx)).catch(() => {});
832
877
  }
833
878
  }
@@ -849,8 +894,9 @@ export {
849
894
  createRouteKey,
850
895
  majorParameter,
851
896
  normalizeRoutePath,
897
+ redactPath,
852
898
  scopeBucket,
853
899
  serializeQuery
854
900
  };
855
901
 
856
- //# debugId=CCF20CDF4862826C64756E2164756E21
902
+ //# debugId=3E6A1976705386FE64756E2164756E21