shardwire 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -63,7 +63,9 @@ var EVENT_REQUIRED_INTENTS = {
63
63
  };
64
64
  function getAvailableEvents(intents) {
65
65
  const enabled = new Set(intents);
66
- return BOT_EVENT_NAMES.filter((eventName) => EVENT_REQUIRED_INTENTS[eventName].every((intent) => enabled.has(intent)));
66
+ return BOT_EVENT_NAMES.filter(
67
+ (eventName) => EVENT_REQUIRED_INTENTS[eventName].every((intent) => enabled.has(intent))
68
+ );
67
69
  }
68
70
 
69
71
  // src/discord/runtime/adapter.ts
@@ -405,10 +407,19 @@ function mapDiscordErrorToActionExecutionError(error) {
405
407
  return new ActionExecutionError("INVALID_REQUEST", message, detailPayload);
406
408
  }
407
409
  if (details.status === 429) {
410
+ let retryAfterMs;
411
+ if (error instanceof DiscordAPIError) {
412
+ const raw = error.rawError;
413
+ const retryAfter = raw?.retry_after;
414
+ if (typeof retryAfter === "number" && Number.isFinite(retryAfter)) {
415
+ retryAfterMs = Math.max(0, Math.ceil(retryAfter * 1e3));
416
+ }
417
+ }
408
418
  return new ActionExecutionError("SERVICE_UNAVAILABLE", message, {
409
419
  discordStatus: 429,
410
420
  retryable: true,
411
- ...details.code !== void 0 ? { discordCode: details.code } : {}
421
+ ...details.code !== void 0 ? { discordCode: details.code } : {},
422
+ ...retryAfterMs !== void 0 ? { retryAfterMs } : {}
412
423
  });
413
424
  }
414
425
  return null;
@@ -726,7 +737,10 @@ var DiscordJsRuntimeAdapter = class {
726
737
  throw mappedError;
727
738
  }
728
739
  this.logger.error("Discord action execution failed.", { action: name, error: String(error) });
729
- throw new ActionExecutionError("INTERNAL_ERROR", error instanceof Error ? error.message : "Discord action failed.");
740
+ throw new ActionExecutionError(
741
+ "INTERNAL_ERROR",
742
+ error instanceof Error ? error.message : "Discord action failed."
743
+ );
730
744
  }
731
745
  }
732
746
  async fetchSendableChannel(channelId) {
@@ -794,7 +808,10 @@ var DiscordJsRuntimeAdapter = class {
794
808
  async replyToInteraction(payload) {
795
809
  const interaction = this.getReplyCapableInteraction(payload.interactionId);
796
810
  if (interaction.replied || interaction.deferred) {
797
- throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has already been acknowledged.`);
811
+ throw new ActionExecutionError(
812
+ "INVALID_REQUEST",
813
+ `Interaction "${payload.interactionId}" has already been acknowledged.`
814
+ );
798
815
  }
799
816
  const reply = await interaction.reply({
800
817
  ...toSendOptions(payload),
@@ -806,7 +823,10 @@ var DiscordJsRuntimeAdapter = class {
806
823
  async deferInteraction(payload) {
807
824
  const interaction = this.getReplyCapableInteraction(payload.interactionId);
808
825
  if (interaction.replied) {
809
- throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has already been replied to.`);
826
+ throw new ActionExecutionError(
827
+ "INVALID_REQUEST",
828
+ `Interaction "${payload.interactionId}" has already been replied to.`
829
+ );
810
830
  }
811
831
  if (!interaction.deferred) {
812
832
  await interaction.deferReply({
@@ -829,7 +849,10 @@ var DiscordJsRuntimeAdapter = class {
829
849
  async followUpInteraction(payload) {
830
850
  const interaction = this.getReplyCapableInteraction(payload.interactionId);
831
851
  if (!interaction.replied && !interaction.deferred) {
832
- throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has not been acknowledged yet.`);
852
+ throw new ActionExecutionError(
853
+ "INVALID_REQUEST",
854
+ `Interaction "${payload.interactionId}" has not been acknowledged yet.`
855
+ );
833
856
  }
834
857
  const followUp = await interaction.followUp({
835
858
  ...toSendOptions(payload),
@@ -938,7 +961,10 @@ var DiscordJsRuntimeAdapter = class {
938
961
  return candidate.emoji.identifier === payload.emoji || candidate.emoji.name === payload.emoji || candidate.emoji.toString() === payload.emoji;
939
962
  });
940
963
  if (!reaction) {
941
- throw new ActionExecutionError("NOT_FOUND", `Reaction "${payload.emoji}" was not found on message "${payload.messageId}".`);
964
+ throw new ActionExecutionError(
965
+ "NOT_FOUND",
966
+ `Reaction "${payload.emoji}" was not found on message "${payload.messageId}".`
967
+ );
942
968
  }
943
969
  await reaction.users.remove(ownUserId);
944
970
  return {
@@ -981,7 +1007,9 @@ function normalizeKindList(value) {
981
1007
  return void 0;
982
1008
  }
983
1009
  const rawValues = Array.isArray(value) ? value : [value];
984
- const normalized = [...new Set(rawValues.filter((entry) => typeof entry === "string"))].sort();
1010
+ const normalized = [
1011
+ ...new Set(rawValues.filter((entry) => typeof entry === "string"))
1012
+ ].sort();
985
1013
  return normalized.length > 0 ? normalized : void 0;
986
1014
  }
987
1015
  function normalizeEventSubscriptionFilter(filter) {
@@ -1247,6 +1275,8 @@ var BridgeTransportServer = class {
1247
1275
  const maxConcurrent = config.options.server.maxConcurrentActions ?? 32;
1248
1276
  const queueTimeout = config.options.server.actionQueueTimeoutMs ?? 5e3;
1249
1277
  this.actionSemaphore = new AsyncSemaphore(maxConcurrent, queueTimeout);
1278
+ this.idempotencyTtlMs = config.options.server.idempotencyTtlMs ?? 12e4;
1279
+ this.idempotencyScope = config.options.server.idempotencyScope ?? "connection";
1250
1280
  this.wss = new WebSocketServer({
1251
1281
  host: config.options.server.host,
1252
1282
  port: config.options.server.port,
@@ -1269,7 +1299,8 @@ var BridgeTransportServer = class {
1269
1299
  stickyEvents = /* @__PURE__ */ new Map();
1270
1300
  actionSemaphore;
1271
1301
  idempotencyCache = /* @__PURE__ */ new Map();
1272
- idempotencyTtlMs = 12e4;
1302
+ idempotencyTtlMs;
1303
+ idempotencyScope;
1273
1304
  authBuckets = /* @__PURE__ */ new Map();
1274
1305
  connectionCount() {
1275
1306
  let count = 0;
@@ -1499,7 +1530,7 @@ var BridgeTransportServer = class {
1499
1530
  }
1500
1531
  const idempotencyRaw = payload.idempotencyKey;
1501
1532
  const idempotencyKey = typeof idempotencyRaw === "string" && idempotencyRaw.length > 0 && idempotencyRaw.length <= 256 ? idempotencyRaw : void 0;
1502
- const idempotencyCacheKey = idempotencyKey ? `${state.id}:${idempotencyKey}` : void 0;
1533
+ const idempotencyCacheKey = idempotencyKey ? this.idempotencyScope === "secret" && activeSecret ? `secret:${activeSecret.id}:${idempotencyKey}` : `conn:${state.id}:${idempotencyKey}` : void 0;
1503
1534
  if (idempotencyCacheKey) {
1504
1535
  this.pruneIdempotencyCache(Date.now());
1505
1536
  const cached = this.idempotencyCache.get(idempotencyCacheKey);
@@ -1512,9 +1543,7 @@ var BridgeTransportServer = class {
1512
1543
  });
1513
1544
  this.safeSend(
1514
1545
  state.socket,
1515
- stringifyEnvelope(
1516
- makeEnvelope(replay.ok ? "action.result" : "action.error", replay, { requestId })
1517
- )
1546
+ stringifyEnvelope(makeEnvelope(replay.ok ? "action.result" : "action.error", replay, { requestId }))
1518
1547
  );
1519
1548
  return;
1520
1549
  }
@@ -1705,11 +1734,7 @@ function normalizeSecretEntry(secret, index) {
1705
1734
  value: scoped.value,
1706
1735
  scope: {
1707
1736
  events: normalizeScopeList(scoped.allow?.events, BOT_EVENT_NAMES, `server.secrets[${index}].allow.events`),
1708
- actions: normalizeScopeList(
1709
- scoped.allow?.actions,
1710
- BOT_ACTION_NAMES,
1711
- `server.secrets[${index}].allow.actions`
1712
- )
1737
+ actions: normalizeScopeList(scoped.allow?.actions, BOT_ACTION_NAMES, `server.secrets[${index}].allow.actions`)
1713
1738
  }
1714
1739
  };
1715
1740
  }
@@ -1736,6 +1761,14 @@ function assertBotBridgeOptions(options) {
1736
1761
  if (options.server.actionQueueTimeoutMs !== void 0) {
1737
1762
  assertPositiveNumber("server.actionQueueTimeoutMs", options.server.actionQueueTimeoutMs);
1738
1763
  }
1764
+ if (options.server.idempotencyScope !== void 0) {
1765
+ if (options.server.idempotencyScope !== "connection" && options.server.idempotencyScope !== "secret") {
1766
+ throw new Error('server.idempotencyScope must be "connection" or "secret".');
1767
+ }
1768
+ }
1769
+ if (options.server.idempotencyTtlMs !== void 0) {
1770
+ assertPositiveNumber("server.idempotencyTtlMs", options.server.idempotencyTtlMs);
1771
+ }
1739
1772
  if (!Array.isArray(options.server.secrets) || options.server.secrets.length === 0) {
1740
1773
  throw new Error("server.secrets must contain at least one secret.");
1741
1774
  }
@@ -1935,6 +1968,24 @@ var AppRequestError = class extends Error {
1935
1968
  code;
1936
1969
  };
1937
1970
  var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
1971
+ function metricsExtrasFromActionError(error) {
1972
+ const details = error.details;
1973
+ if (!details || typeof details !== "object" || Array.isArray(details)) {
1974
+ return {};
1975
+ }
1976
+ const obj = details;
1977
+ const extras = {};
1978
+ if (typeof obj.retryAfterMs === "number" && Number.isFinite(obj.retryAfterMs)) {
1979
+ extras.retryAfterMs = obj.retryAfterMs;
1980
+ }
1981
+ if (typeof obj.discordStatus === "number" && Number.isFinite(obj.discordStatus)) {
1982
+ extras.discordStatus = obj.discordStatus;
1983
+ }
1984
+ if (typeof obj.discordCode === "number" && Number.isFinite(obj.discordCode)) {
1985
+ extras.discordCode = obj.discordCode;
1986
+ }
1987
+ return extras;
1988
+ }
1938
1989
  function connectBotBridge(options) {
1939
1990
  assertAppBridgeOptions(options);
1940
1991
  const logger = withLogger(options.logger);
@@ -2243,7 +2294,7 @@ function connectBotBridge(options) {
2243
2294
  requestId,
2244
2295
  durationMs: Date.now() - started,
2245
2296
  ok: result.ok,
2246
- ...!result.ok ? { errorCode: result.error.code } : {}
2297
+ ...!result.ok ? { errorCode: result.error.code, ...metricsExtrasFromActionError(result.error) } : {}
2247
2298
  });
2248
2299
  return result;
2249
2300
  } catch (error) {