argus-discord-analytics 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/index.d.mts CHANGED
@@ -21,7 +21,7 @@
21
21
  * argus.trackRevenue({ source: 'patreon', amount: 500, tier: 'Premium' });
22
22
  * ```
23
23
  */
24
- declare const SDK_VERSION = "0.3.0";
24
+ declare const SDK_VERSION = "0.4.0";
25
25
  interface ArgusConfig {
26
26
  /** Your Argus API key (starts with arg_live_ or arg_test_) */
27
27
  apiKey: string;
@@ -75,6 +75,40 @@ interface HeartbeatConfig {
75
75
  interval?: number;
76
76
  /** Include latency measurement */
77
77
  measureLatency?: boolean;
78
+ /** Shard ID for sharded bots */
79
+ shardId?: number;
80
+ /** Total number of shards */
81
+ shardCount?: number;
82
+ /** Guild count for this shard */
83
+ guildCount?: number;
84
+ }
85
+ interface GatewayEventConfig {
86
+ /** Shard ID the event occurred on */
87
+ shardId?: number;
88
+ /** Rate limit bucket (for rate_limit events) */
89
+ bucket?: string;
90
+ /** Retry after in ms (for rate_limit events) */
91
+ retryAfter?: number;
92
+ /** Additional metadata */
93
+ metadata?: Record<string, unknown>;
94
+ }
95
+ interface ResourceMetrics {
96
+ /** CPU usage percentage (0-100) */
97
+ cpuPercent?: number;
98
+ /** Total memory usage in MB */
99
+ memoryMb?: number;
100
+ /** Heap used in MB */
101
+ heapUsedMb?: number;
102
+ /** Heap total in MB */
103
+ heapTotalMb?: number;
104
+ /** Number of guilds/servers */
105
+ guildCount?: number;
106
+ /** Number of cached users */
107
+ cachedUsers?: number;
108
+ /** Number of cached channels */
109
+ cachedChannels?: number;
110
+ /** Shard ID (optional for per-shard metrics) */
111
+ shardId?: number;
78
112
  }
79
113
  interface ServerEvent {
80
114
  type: 'server';
@@ -113,6 +147,9 @@ declare class Argus {
113
147
  private botId;
114
148
  private isHeartbeatEnabled;
115
149
  private commandStartTimes;
150
+ private shardId;
151
+ private shardCount;
152
+ private guildCount;
116
153
  constructor(apiKeyOrConfig: string | ArgusConfig);
117
154
  private log;
118
155
  private startFlushTimer;
@@ -331,6 +368,12 @@ declare class Argus {
331
368
  *
332
369
  * // Or customize the interval
333
370
  * argus.startHeartbeat({ interval: 60000 }); // 1 minute
371
+ *
372
+ * // For sharded bots - track per-shard status
373
+ * argus.startHeartbeat({
374
+ * shardId: shard.id,
375
+ * shardCount: client.shard?.count || 1
376
+ * });
334
377
  * ```
335
378
  */
336
379
  startHeartbeat(config?: HeartbeatConfig): void;
@@ -342,6 +385,83 @@ declare class Argus {
342
385
  * Send a single heartbeat ping
343
386
  */
344
387
  private sendHeartbeat;
388
+ /**
389
+ * Track a gateway event (reconnect, rate limit, etc.)
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * // Track reconnects
394
+ * client.on('shardReconnecting', (shardId) => {
395
+ * argus.trackGatewayEvent('reconnect', { shardId });
396
+ * });
397
+ *
398
+ * // Track rate limits
399
+ * client.rest.on('rateLimited', (info) => {
400
+ * argus.trackGatewayEvent('rate_limit', {
401
+ * bucket: info.route,
402
+ * retryAfter: info.retryAfter,
403
+ * });
404
+ * });
405
+ * ```
406
+ */
407
+ trackGatewayEvent(eventType: 'reconnect' | 'rate_limit' | 'ready' | 'resume' | 'disconnect' | 'death' | 'spawn', config?: GatewayEventConfig): Promise<void>;
408
+ /**
409
+ * Track resource usage (CPU, memory, cache sizes)
410
+ * Call this periodically (e.g., every 60 seconds) to monitor resource usage
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * // Track resources every minute
415
+ * setInterval(() => {
416
+ * argus.trackResources({
417
+ * memoryMb: process.memoryUsage().heapUsed / 1024 / 1024,
418
+ * guildCount: client.guilds.cache.size,
419
+ * cachedUsers: client.users.cache.size,
420
+ * });
421
+ * }, 60000);
422
+ * ```
423
+ */
424
+ trackResources(metrics: ResourceMetrics): Promise<void>;
425
+ /**
426
+ * Attach to a Discord.js ShardingManager to auto-track shard events
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const manager = new ShardingManager('./bot.js', { ... });
431
+ * argus.attachToShardManager(manager);
432
+ * manager.spawn();
433
+ * ```
434
+ */
435
+ attachToShardManager(manager: {
436
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
437
+ shardList?: number[];
438
+ totalShards?: number | 'auto';
439
+ }): void;
440
+ /**
441
+ * Set up automatic tracking for Discord.js client shard events
442
+ * Call this from within your bot process to track per-shard events
443
+ *
444
+ * @example
445
+ * ```typescript
446
+ * // In your bot.js that runs in each shard
447
+ * argus.attachToClient(client);
448
+ * ```
449
+ */
450
+ attachToClient(client: {
451
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
452
+ shard?: {
453
+ ids: number[];
454
+ count: number;
455
+ } | null;
456
+ guilds?: {
457
+ cache: {
458
+ size: number;
459
+ };
460
+ };
461
+ rest?: {
462
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
463
+ };
464
+ }): void;
345
465
  /**
346
466
  * Start timing a command execution
347
467
  * Call this at the beginning of your command handler
@@ -386,4 +506,4 @@ declare class Argus {
386
506
  shutdown(): Promise<void>;
387
507
  }
388
508
 
389
- export { Argus, type ArgusConfig, type HeartbeatConfig, type RevenueEvent, SDK_VERSION, type ServerEvent, type TrackEvent, type TrackOptions, Argus as default };
509
+ export { Argus, type ArgusConfig, type GatewayEventConfig, type HeartbeatConfig, type ResourceMetrics, type RevenueEvent, SDK_VERSION, type ServerEvent, type TrackEvent, type TrackOptions, Argus as default };
package/dist/index.d.ts CHANGED
@@ -21,7 +21,7 @@
21
21
  * argus.trackRevenue({ source: 'patreon', amount: 500, tier: 'Premium' });
22
22
  * ```
23
23
  */
24
- declare const SDK_VERSION = "0.3.0";
24
+ declare const SDK_VERSION = "0.4.0";
25
25
  interface ArgusConfig {
26
26
  /** Your Argus API key (starts with arg_live_ or arg_test_) */
27
27
  apiKey: string;
@@ -75,6 +75,40 @@ interface HeartbeatConfig {
75
75
  interval?: number;
76
76
  /** Include latency measurement */
77
77
  measureLatency?: boolean;
78
+ /** Shard ID for sharded bots */
79
+ shardId?: number;
80
+ /** Total number of shards */
81
+ shardCount?: number;
82
+ /** Guild count for this shard */
83
+ guildCount?: number;
84
+ }
85
+ interface GatewayEventConfig {
86
+ /** Shard ID the event occurred on */
87
+ shardId?: number;
88
+ /** Rate limit bucket (for rate_limit events) */
89
+ bucket?: string;
90
+ /** Retry after in ms (for rate_limit events) */
91
+ retryAfter?: number;
92
+ /** Additional metadata */
93
+ metadata?: Record<string, unknown>;
94
+ }
95
+ interface ResourceMetrics {
96
+ /** CPU usage percentage (0-100) */
97
+ cpuPercent?: number;
98
+ /** Total memory usage in MB */
99
+ memoryMb?: number;
100
+ /** Heap used in MB */
101
+ heapUsedMb?: number;
102
+ /** Heap total in MB */
103
+ heapTotalMb?: number;
104
+ /** Number of guilds/servers */
105
+ guildCount?: number;
106
+ /** Number of cached users */
107
+ cachedUsers?: number;
108
+ /** Number of cached channels */
109
+ cachedChannels?: number;
110
+ /** Shard ID (optional for per-shard metrics) */
111
+ shardId?: number;
78
112
  }
79
113
  interface ServerEvent {
80
114
  type: 'server';
@@ -113,6 +147,9 @@ declare class Argus {
113
147
  private botId;
114
148
  private isHeartbeatEnabled;
115
149
  private commandStartTimes;
150
+ private shardId;
151
+ private shardCount;
152
+ private guildCount;
116
153
  constructor(apiKeyOrConfig: string | ArgusConfig);
117
154
  private log;
118
155
  private startFlushTimer;
@@ -331,6 +368,12 @@ declare class Argus {
331
368
  *
332
369
  * // Or customize the interval
333
370
  * argus.startHeartbeat({ interval: 60000 }); // 1 minute
371
+ *
372
+ * // For sharded bots - track per-shard status
373
+ * argus.startHeartbeat({
374
+ * shardId: shard.id,
375
+ * shardCount: client.shard?.count || 1
376
+ * });
334
377
  * ```
335
378
  */
336
379
  startHeartbeat(config?: HeartbeatConfig): void;
@@ -342,6 +385,83 @@ declare class Argus {
342
385
  * Send a single heartbeat ping
343
386
  */
344
387
  private sendHeartbeat;
388
+ /**
389
+ * Track a gateway event (reconnect, rate limit, etc.)
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * // Track reconnects
394
+ * client.on('shardReconnecting', (shardId) => {
395
+ * argus.trackGatewayEvent('reconnect', { shardId });
396
+ * });
397
+ *
398
+ * // Track rate limits
399
+ * client.rest.on('rateLimited', (info) => {
400
+ * argus.trackGatewayEvent('rate_limit', {
401
+ * bucket: info.route,
402
+ * retryAfter: info.retryAfter,
403
+ * });
404
+ * });
405
+ * ```
406
+ */
407
+ trackGatewayEvent(eventType: 'reconnect' | 'rate_limit' | 'ready' | 'resume' | 'disconnect' | 'death' | 'spawn', config?: GatewayEventConfig): Promise<void>;
408
+ /**
409
+ * Track resource usage (CPU, memory, cache sizes)
410
+ * Call this periodically (e.g., every 60 seconds) to monitor resource usage
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * // Track resources every minute
415
+ * setInterval(() => {
416
+ * argus.trackResources({
417
+ * memoryMb: process.memoryUsage().heapUsed / 1024 / 1024,
418
+ * guildCount: client.guilds.cache.size,
419
+ * cachedUsers: client.users.cache.size,
420
+ * });
421
+ * }, 60000);
422
+ * ```
423
+ */
424
+ trackResources(metrics: ResourceMetrics): Promise<void>;
425
+ /**
426
+ * Attach to a Discord.js ShardingManager to auto-track shard events
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const manager = new ShardingManager('./bot.js', { ... });
431
+ * argus.attachToShardManager(manager);
432
+ * manager.spawn();
433
+ * ```
434
+ */
435
+ attachToShardManager(manager: {
436
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
437
+ shardList?: number[];
438
+ totalShards?: number | 'auto';
439
+ }): void;
440
+ /**
441
+ * Set up automatic tracking for Discord.js client shard events
442
+ * Call this from within your bot process to track per-shard events
443
+ *
444
+ * @example
445
+ * ```typescript
446
+ * // In your bot.js that runs in each shard
447
+ * argus.attachToClient(client);
448
+ * ```
449
+ */
450
+ attachToClient(client: {
451
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
452
+ shard?: {
453
+ ids: number[];
454
+ count: number;
455
+ } | null;
456
+ guilds?: {
457
+ cache: {
458
+ size: number;
459
+ };
460
+ };
461
+ rest?: {
462
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
463
+ };
464
+ }): void;
345
465
  /**
346
466
  * Start timing a command execution
347
467
  * Call this at the beginning of your command handler
@@ -386,4 +506,4 @@ declare class Argus {
386
506
  shutdown(): Promise<void>;
387
507
  }
388
508
 
389
- export { Argus, type ArgusConfig, type HeartbeatConfig, type RevenueEvent, SDK_VERSION, type ServerEvent, type TrackEvent, type TrackOptions, Argus as default };
509
+ export { Argus, type ArgusConfig, type GatewayEventConfig, type HeartbeatConfig, type ResourceMetrics, type RevenueEvent, SDK_VERSION, type ServerEvent, type TrackEvent, type TrackOptions, Argus as default };
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ __export(index_exports, {
25
25
  default: () => index_default
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
- var SDK_VERSION = "0.3.0";
28
+ var SDK_VERSION = "0.4.0";
29
29
  function hashString(str) {
30
30
  let hash = 0;
31
31
  for (let i = 0; i < str.length; i++) {
@@ -494,6 +494,12 @@ var Argus = class {
494
494
  *
495
495
  * // Or customize the interval
496
496
  * argus.startHeartbeat({ interval: 60000 }); // 1 minute
497
+ *
498
+ * // For sharded bots - track per-shard status
499
+ * argus.startHeartbeat({
500
+ * shardId: shard.id,
501
+ * shardCount: client.shard?.count || 1
502
+ * });
497
503
  * ```
498
504
  */
499
505
  startHeartbeat(config) {
@@ -503,11 +509,21 @@ var Argus = class {
503
509
  }
504
510
  const interval = config?.interval || this.heartbeatInterval;
505
511
  this.isHeartbeatEnabled = true;
512
+ if (config?.shardId !== void 0) {
513
+ this.shardId = config.shardId;
514
+ }
515
+ if (config?.shardCount !== void 0) {
516
+ this.shardCount = config.shardCount;
517
+ }
518
+ if (config?.guildCount !== void 0) {
519
+ this.guildCount = config.guildCount;
520
+ }
506
521
  this.sendHeartbeat();
507
522
  this.heartbeatTimer = setInterval(() => {
508
523
  this.sendHeartbeat();
509
524
  }, interval);
510
- this.log(`Heartbeat started with ${interval}ms interval`);
525
+ const shardInfo = this.shardId !== void 0 ? ` (shard ${this.shardId}/${this.shardCount})` : "";
526
+ this.log(`Heartbeat started with ${interval}ms interval${shardInfo}`);
511
527
  }
512
528
  /**
513
529
  * Stop sending heartbeat pings
@@ -526,26 +542,194 @@ var Argus = class {
526
542
  async sendHeartbeat() {
527
543
  const startTime = Date.now();
528
544
  try {
529
- const response = await fetch(`${this.endpoint}/api/heartbeat`, {
545
+ const endpoint = this.shardId !== void 0 ? `${this.endpoint}/api/shard-heartbeat` : `${this.endpoint}/api/heartbeat`;
546
+ const body = { timestamp: startTime };
547
+ if (this.shardId !== void 0) {
548
+ body.shardId = this.shardId;
549
+ body.shardCount = this.shardCount || 1;
550
+ body.guildCount = this.guildCount;
551
+ }
552
+ const response = await fetch(endpoint, {
530
553
  method: "POST",
531
554
  headers: {
532
555
  "Content-Type": "application/json",
533
556
  "Authorization": `Bearer ${this.apiKey}`
534
557
  },
535
- body: JSON.stringify({
536
- timestamp: startTime
537
- })
558
+ body: JSON.stringify(body)
538
559
  });
539
560
  const latencyMs = Date.now() - startTime;
540
561
  if (!response.ok) {
541
562
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
542
563
  }
543
- this.log(`Heartbeat sent (${latencyMs}ms)`);
564
+ const shardInfo = this.shardId !== void 0 ? ` [shard ${this.shardId}]` : "";
565
+ this.log(`Heartbeat sent (${latencyMs}ms)${shardInfo}`);
544
566
  } catch (error) {
545
567
  this.log("Heartbeat failed:", error);
546
568
  }
547
569
  }
548
570
  // ==========================================
571
+ // Gateway & Infrastructure Events
572
+ // ==========================================
573
+ /**
574
+ * Track a gateway event (reconnect, rate limit, etc.)
575
+ *
576
+ * @example
577
+ * ```typescript
578
+ * // Track reconnects
579
+ * client.on('shardReconnecting', (shardId) => {
580
+ * argus.trackGatewayEvent('reconnect', { shardId });
581
+ * });
582
+ *
583
+ * // Track rate limits
584
+ * client.rest.on('rateLimited', (info) => {
585
+ * argus.trackGatewayEvent('rate_limit', {
586
+ * bucket: info.route,
587
+ * retryAfter: info.retryAfter,
588
+ * });
589
+ * });
590
+ * ```
591
+ */
592
+ async trackGatewayEvent(eventType, config) {
593
+ try {
594
+ const response = await fetch(`${this.endpoint}/api/infrastructure-events`, {
595
+ method: "POST",
596
+ headers: {
597
+ "Content-Type": "application/json",
598
+ "Authorization": `Bearer ${this.apiKey}`
599
+ },
600
+ body: JSON.stringify({
601
+ events: [{
602
+ type: eventType,
603
+ shardId: config?.shardId ?? this.shardId,
604
+ metadata: {
605
+ bucket: config?.bucket,
606
+ retryAfter: config?.retryAfter,
607
+ ...config?.metadata
608
+ },
609
+ timestamp: Date.now()
610
+ }]
611
+ })
612
+ });
613
+ if (!response.ok) {
614
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
615
+ }
616
+ this.log(`Gateway event tracked: ${eventType}`, config?.shardId !== void 0 ? `[shard ${config.shardId}]` : "");
617
+ } catch (error) {
618
+ this.log("Failed to track gateway event:", error);
619
+ }
620
+ }
621
+ /**
622
+ * Track resource usage (CPU, memory, cache sizes)
623
+ * Call this periodically (e.g., every 60 seconds) to monitor resource usage
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * // Track resources every minute
628
+ * setInterval(() => {
629
+ * argus.trackResources({
630
+ * memoryMb: process.memoryUsage().heapUsed / 1024 / 1024,
631
+ * guildCount: client.guilds.cache.size,
632
+ * cachedUsers: client.users.cache.size,
633
+ * });
634
+ * }, 60000);
635
+ * ```
636
+ */
637
+ async trackResources(metrics) {
638
+ try {
639
+ const response = await fetch(`${this.endpoint}/api/resources`, {
640
+ method: "POST",
641
+ headers: {
642
+ "Content-Type": "application/json",
643
+ "Authorization": `Bearer ${this.apiKey}`
644
+ },
645
+ body: JSON.stringify({
646
+ cpuPercent: metrics.cpuPercent,
647
+ memoryMb: metrics.memoryMb,
648
+ heapUsedMb: metrics.heapUsedMb,
649
+ heapTotalMb: metrics.heapTotalMb,
650
+ guildCount: metrics.guildCount,
651
+ cachedUsers: metrics.cachedUsers,
652
+ cachedChannels: metrics.cachedChannels,
653
+ shardId: metrics.shardId ?? this.shardId
654
+ })
655
+ });
656
+ if (!response.ok) {
657
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
658
+ }
659
+ this.log("Resource metrics tracked");
660
+ } catch (error) {
661
+ this.log("Failed to track resources:", error);
662
+ }
663
+ }
664
+ /**
665
+ * Attach to a Discord.js ShardingManager to auto-track shard events
666
+ *
667
+ * @example
668
+ * ```typescript
669
+ * const manager = new ShardingManager('./bot.js', { ... });
670
+ * argus.attachToShardManager(manager);
671
+ * manager.spawn();
672
+ * ```
673
+ */
674
+ attachToShardManager(manager) {
675
+ this.log("Attaching to ShardingManager");
676
+ manager.on("shardCreate", (...args) => {
677
+ const shard = args[0];
678
+ this.trackGatewayEvent("spawn", { shardId: shard.id });
679
+ });
680
+ this.log("ShardingManager attached - spawn events will be tracked");
681
+ }
682
+ /**
683
+ * Set up automatic tracking for Discord.js client shard events
684
+ * Call this from within your bot process to track per-shard events
685
+ *
686
+ * @example
687
+ * ```typescript
688
+ * // In your bot.js that runs in each shard
689
+ * argus.attachToClient(client);
690
+ * ```
691
+ */
692
+ attachToClient(client) {
693
+ if (client.shard) {
694
+ this.shardId = client.shard.ids[0];
695
+ this.shardCount = client.shard.count;
696
+ }
697
+ if (client.guilds?.cache) {
698
+ this.guildCount = client.guilds.cache.size;
699
+ }
700
+ client.on("ready", () => {
701
+ this.trackGatewayEvent("ready", { shardId: this.shardId });
702
+ });
703
+ client.on("shardReady", (...args) => {
704
+ const id = args[0];
705
+ this.trackGatewayEvent("ready", { shardId: id });
706
+ });
707
+ client.on("shardReconnecting", (...args) => {
708
+ const id = args[0];
709
+ this.trackGatewayEvent("reconnect", { shardId: id });
710
+ });
711
+ client.on("shardResume", (...args) => {
712
+ const id = args[0];
713
+ this.trackGatewayEvent("resume", { shardId: id });
714
+ });
715
+ client.on("shardDisconnect", (...args) => {
716
+ const id = args[1];
717
+ this.trackGatewayEvent("disconnect", { shardId: id });
718
+ });
719
+ if (client.rest) {
720
+ client.rest.on("rateLimited", (...args) => {
721
+ const info = args[0];
722
+ this.trackGatewayEvent("rate_limit", {
723
+ shardId: this.shardId,
724
+ bucket: info.route,
725
+ retryAfter: info.timeToReset,
726
+ metadata: { global: info.global }
727
+ });
728
+ });
729
+ }
730
+ this.log("Discord.js client attached - shard events will be tracked");
731
+ }
732
+ // ==========================================
549
733
  // Latency Tracking
550
734
  // ==========================================
551
735
  /**
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var SDK_VERSION = "0.3.0";
2
+ var SDK_VERSION = "0.4.0";
3
3
  function hashString(str) {
4
4
  let hash = 0;
5
5
  for (let i = 0; i < str.length; i++) {
@@ -468,6 +468,12 @@ var Argus = class {
468
468
  *
469
469
  * // Or customize the interval
470
470
  * argus.startHeartbeat({ interval: 60000 }); // 1 minute
471
+ *
472
+ * // For sharded bots - track per-shard status
473
+ * argus.startHeartbeat({
474
+ * shardId: shard.id,
475
+ * shardCount: client.shard?.count || 1
476
+ * });
471
477
  * ```
472
478
  */
473
479
  startHeartbeat(config) {
@@ -477,11 +483,21 @@ var Argus = class {
477
483
  }
478
484
  const interval = config?.interval || this.heartbeatInterval;
479
485
  this.isHeartbeatEnabled = true;
486
+ if (config?.shardId !== void 0) {
487
+ this.shardId = config.shardId;
488
+ }
489
+ if (config?.shardCount !== void 0) {
490
+ this.shardCount = config.shardCount;
491
+ }
492
+ if (config?.guildCount !== void 0) {
493
+ this.guildCount = config.guildCount;
494
+ }
480
495
  this.sendHeartbeat();
481
496
  this.heartbeatTimer = setInterval(() => {
482
497
  this.sendHeartbeat();
483
498
  }, interval);
484
- this.log(`Heartbeat started with ${interval}ms interval`);
499
+ const shardInfo = this.shardId !== void 0 ? ` (shard ${this.shardId}/${this.shardCount})` : "";
500
+ this.log(`Heartbeat started with ${interval}ms interval${shardInfo}`);
485
501
  }
486
502
  /**
487
503
  * Stop sending heartbeat pings
@@ -500,26 +516,194 @@ var Argus = class {
500
516
  async sendHeartbeat() {
501
517
  const startTime = Date.now();
502
518
  try {
503
- const response = await fetch(`${this.endpoint}/api/heartbeat`, {
519
+ const endpoint = this.shardId !== void 0 ? `${this.endpoint}/api/shard-heartbeat` : `${this.endpoint}/api/heartbeat`;
520
+ const body = { timestamp: startTime };
521
+ if (this.shardId !== void 0) {
522
+ body.shardId = this.shardId;
523
+ body.shardCount = this.shardCount || 1;
524
+ body.guildCount = this.guildCount;
525
+ }
526
+ const response = await fetch(endpoint, {
504
527
  method: "POST",
505
528
  headers: {
506
529
  "Content-Type": "application/json",
507
530
  "Authorization": `Bearer ${this.apiKey}`
508
531
  },
509
- body: JSON.stringify({
510
- timestamp: startTime
511
- })
532
+ body: JSON.stringify(body)
512
533
  });
513
534
  const latencyMs = Date.now() - startTime;
514
535
  if (!response.ok) {
515
536
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
516
537
  }
517
- this.log(`Heartbeat sent (${latencyMs}ms)`);
538
+ const shardInfo = this.shardId !== void 0 ? ` [shard ${this.shardId}]` : "";
539
+ this.log(`Heartbeat sent (${latencyMs}ms)${shardInfo}`);
518
540
  } catch (error) {
519
541
  this.log("Heartbeat failed:", error);
520
542
  }
521
543
  }
522
544
  // ==========================================
545
+ // Gateway & Infrastructure Events
546
+ // ==========================================
547
+ /**
548
+ * Track a gateway event (reconnect, rate limit, etc.)
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * // Track reconnects
553
+ * client.on('shardReconnecting', (shardId) => {
554
+ * argus.trackGatewayEvent('reconnect', { shardId });
555
+ * });
556
+ *
557
+ * // Track rate limits
558
+ * client.rest.on('rateLimited', (info) => {
559
+ * argus.trackGatewayEvent('rate_limit', {
560
+ * bucket: info.route,
561
+ * retryAfter: info.retryAfter,
562
+ * });
563
+ * });
564
+ * ```
565
+ */
566
+ async trackGatewayEvent(eventType, config) {
567
+ try {
568
+ const response = await fetch(`${this.endpoint}/api/infrastructure-events`, {
569
+ method: "POST",
570
+ headers: {
571
+ "Content-Type": "application/json",
572
+ "Authorization": `Bearer ${this.apiKey}`
573
+ },
574
+ body: JSON.stringify({
575
+ events: [{
576
+ type: eventType,
577
+ shardId: config?.shardId ?? this.shardId,
578
+ metadata: {
579
+ bucket: config?.bucket,
580
+ retryAfter: config?.retryAfter,
581
+ ...config?.metadata
582
+ },
583
+ timestamp: Date.now()
584
+ }]
585
+ })
586
+ });
587
+ if (!response.ok) {
588
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
589
+ }
590
+ this.log(`Gateway event tracked: ${eventType}`, config?.shardId !== void 0 ? `[shard ${config.shardId}]` : "");
591
+ } catch (error) {
592
+ this.log("Failed to track gateway event:", error);
593
+ }
594
+ }
595
+ /**
596
+ * Track resource usage (CPU, memory, cache sizes)
597
+ * Call this periodically (e.g., every 60 seconds) to monitor resource usage
598
+ *
599
+ * @example
600
+ * ```typescript
601
+ * // Track resources every minute
602
+ * setInterval(() => {
603
+ * argus.trackResources({
604
+ * memoryMb: process.memoryUsage().heapUsed / 1024 / 1024,
605
+ * guildCount: client.guilds.cache.size,
606
+ * cachedUsers: client.users.cache.size,
607
+ * });
608
+ * }, 60000);
609
+ * ```
610
+ */
611
+ async trackResources(metrics) {
612
+ try {
613
+ const response = await fetch(`${this.endpoint}/api/resources`, {
614
+ method: "POST",
615
+ headers: {
616
+ "Content-Type": "application/json",
617
+ "Authorization": `Bearer ${this.apiKey}`
618
+ },
619
+ body: JSON.stringify({
620
+ cpuPercent: metrics.cpuPercent,
621
+ memoryMb: metrics.memoryMb,
622
+ heapUsedMb: metrics.heapUsedMb,
623
+ heapTotalMb: metrics.heapTotalMb,
624
+ guildCount: metrics.guildCount,
625
+ cachedUsers: metrics.cachedUsers,
626
+ cachedChannels: metrics.cachedChannels,
627
+ shardId: metrics.shardId ?? this.shardId
628
+ })
629
+ });
630
+ if (!response.ok) {
631
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
632
+ }
633
+ this.log("Resource metrics tracked");
634
+ } catch (error) {
635
+ this.log("Failed to track resources:", error);
636
+ }
637
+ }
638
+ /**
639
+ * Attach to a Discord.js ShardingManager to auto-track shard events
640
+ *
641
+ * @example
642
+ * ```typescript
643
+ * const manager = new ShardingManager('./bot.js', { ... });
644
+ * argus.attachToShardManager(manager);
645
+ * manager.spawn();
646
+ * ```
647
+ */
648
+ attachToShardManager(manager) {
649
+ this.log("Attaching to ShardingManager");
650
+ manager.on("shardCreate", (...args) => {
651
+ const shard = args[0];
652
+ this.trackGatewayEvent("spawn", { shardId: shard.id });
653
+ });
654
+ this.log("ShardingManager attached - spawn events will be tracked");
655
+ }
656
+ /**
657
+ * Set up automatic tracking for Discord.js client shard events
658
+ * Call this from within your bot process to track per-shard events
659
+ *
660
+ * @example
661
+ * ```typescript
662
+ * // In your bot.js that runs in each shard
663
+ * argus.attachToClient(client);
664
+ * ```
665
+ */
666
+ attachToClient(client) {
667
+ if (client.shard) {
668
+ this.shardId = client.shard.ids[0];
669
+ this.shardCount = client.shard.count;
670
+ }
671
+ if (client.guilds?.cache) {
672
+ this.guildCount = client.guilds.cache.size;
673
+ }
674
+ client.on("ready", () => {
675
+ this.trackGatewayEvent("ready", { shardId: this.shardId });
676
+ });
677
+ client.on("shardReady", (...args) => {
678
+ const id = args[0];
679
+ this.trackGatewayEvent("ready", { shardId: id });
680
+ });
681
+ client.on("shardReconnecting", (...args) => {
682
+ const id = args[0];
683
+ this.trackGatewayEvent("reconnect", { shardId: id });
684
+ });
685
+ client.on("shardResume", (...args) => {
686
+ const id = args[0];
687
+ this.trackGatewayEvent("resume", { shardId: id });
688
+ });
689
+ client.on("shardDisconnect", (...args) => {
690
+ const id = args[1];
691
+ this.trackGatewayEvent("disconnect", { shardId: id });
692
+ });
693
+ if (client.rest) {
694
+ client.rest.on("rateLimited", (...args) => {
695
+ const info = args[0];
696
+ this.trackGatewayEvent("rate_limit", {
697
+ shardId: this.shardId,
698
+ bucket: info.route,
699
+ retryAfter: info.timeToReset,
700
+ metadata: { global: info.global }
701
+ });
702
+ });
703
+ }
704
+ this.log("Discord.js client attached - shard events will be tracked");
705
+ }
706
+ // ==========================================
523
707
  // Latency Tracking
524
708
  // ==========================================
525
709
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argus-discord-analytics",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "The all-seeing analytics SDK for Discord bots - track commands, errors, servers, and more",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",