octokit-load-balancer 1.0.3 → 2.0.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/README.md CHANGED
@@ -57,8 +57,6 @@ Every time you call `getApp(config)`:
57
57
  | `apps` | `GitHubAppConfig[]` | Yes | Array of app configurations |
58
58
  | `baseUrl` | `string` | Yes | GitHub API base URL |
59
59
 
60
- App configs use `GitHubAppConfig` (re-exported from `@octokit/auth-app`'s `StrategyOptions`). All apps must have valid `appId` and `privateKey` — incomplete configs will throw an error.
61
-
62
60
  ## Scalability
63
61
 
64
62
  This library is designed for high-throughput scenarios where a single GitHub App's rate limit (5,000 requests/hour) isn't enough. By distributing requests across multiple apps, you get N × 5,000 requests/hour.
package/dist/index.d.mts CHANGED
@@ -34,7 +34,6 @@ interface RateLimitInfo extends RateLimitData {
34
34
 
35
35
  /**
36
36
  * Get the Octokit instance with the most available rate limit.
37
- * Pass your app configurations directly - incomplete configs are filtered out.
38
37
  *
39
38
  * @example
40
39
  * ```typescript
@@ -59,7 +58,7 @@ interface RateLimitInfo extends RateLimitData {
59
58
  * await octokit.rest.repos.get({ owner: 'org', repo: 'repo' })
60
59
  * ```
61
60
  *
62
- * @throws {Error} When no valid app configurations are provided
61
+ * @throws {Error} When no apps are provided or all apps have exhausted their rate limits
63
62
  */
64
63
  declare function getApp(config: GitHubAppPoolConfig): Promise<Octokit>;
65
64
 
package/dist/index.d.ts CHANGED
@@ -34,7 +34,6 @@ interface RateLimitInfo extends RateLimitData {
34
34
 
35
35
  /**
36
36
  * Get the Octokit instance with the most available rate limit.
37
- * Pass your app configurations directly - incomplete configs are filtered out.
38
37
  *
39
38
  * @example
40
39
  * ```typescript
@@ -59,7 +58,7 @@ interface RateLimitInfo extends RateLimitData {
59
58
  * await octokit.rest.repos.get({ owner: 'org', repo: 'repo' })
60
59
  * ```
61
60
  *
62
- * @throws {Error} When no valid app configurations are provided
61
+ * @throws {Error} When no apps are provided or all apps have exhausted their rate limits
63
62
  */
64
63
  declare function getApp(config: GitHubAppPoolConfig): Promise<Octokit>;
65
64
 
package/dist/index.js CHANGED
@@ -29,20 +29,8 @@ var import_auth_app = require("@octokit/auth-app");
29
29
  var import_octokit = require("octokit");
30
30
  var DEBUG_KEY = "octokit-load-balancer";
31
31
  async function getApp(config) {
32
- if (!Array.isArray(config?.apps)) {
33
- throw new Error("Invalid config: apps must be an array");
34
- }
35
- if (typeof config?.baseUrl !== "string" || !config.baseUrl) {
36
- throw new Error("Invalid config: baseUrl must be a non-empty string");
37
- }
38
- if (config.apps.length === 0) {
39
- throw new Error("Invalid config: apps array is empty");
40
- }
41
- const invalidApps = config.apps.filter((app) => !isCompleteConfig(app));
42
- if (invalidApps.length > 0) {
43
- throw new Error(
44
- `Invalid config: ${invalidApps.length} app(s) missing required appId or privateKey`
45
- );
32
+ if (!config?.apps?.length) {
33
+ throw new Error("No apps provided");
46
34
  }
47
35
  log(`Using ${config.apps.length} app configs`);
48
36
  const octokits = config.apps.map((app) => createOctokit(app, config.baseUrl));
@@ -54,9 +42,9 @@ async function getApp(config) {
54
42
  rateLimits.map((r) => `app[${r.appIndex}]: ${r.remaining}/${r.limit}`).join(", ")
55
43
  );
56
44
  const { bestIndex, rateLimit } = rateLimits.reduce(
57
- (acc, cur, i) => {
45
+ (acc, cur) => {
58
46
  if (cur.remaining > acc.rateLimit.remaining) {
59
- return { bestIndex: i, rateLimit: cur };
47
+ return { bestIndex: cur.appIndex, rateLimit: cur };
60
48
  }
61
49
  return acc;
62
50
  },
@@ -70,9 +58,6 @@ async function getApp(config) {
70
58
  );
71
59
  return octokits[bestIndex];
72
60
  }
73
- function isCompleteConfig(config) {
74
- return typeof config === "object" && config !== null && "appId" in config && "privateKey" in config && !!(config.appId && config.privateKey);
75
- }
76
61
  function decodePrivateKey(key) {
77
62
  if (key.startsWith("-----BEGIN ") && key.includes("-----END ")) {
78
63
  return key;
@@ -84,9 +69,9 @@ function createOctokit(appConfig, baseUrl) {
84
69
  authStrategy: import_auth_app.createAppAuth,
85
70
  baseUrl,
86
71
  auth: {
87
- appId: appConfig.appId,
88
- installationId: appConfig.installationId,
89
- privateKey: decodePrivateKey(appConfig.privateKey)
72
+ ...appConfig,
73
+ // TODO: this type check can be removed once the issue will be fixed in @octokit/auth-app
74
+ privateKey: appConfig.privateKey && typeof appConfig.privateKey === "string" ? decodePrivateKey(appConfig.privateKey) : void 0
90
75
  }
91
76
  });
92
77
  }
package/dist/index.mjs CHANGED
@@ -3,20 +3,8 @@ import { createAppAuth } from "@octokit/auth-app";
3
3
  import { Octokit } from "octokit";
4
4
  var DEBUG_KEY = "octokit-load-balancer";
5
5
  async function getApp(config) {
6
- if (!Array.isArray(config?.apps)) {
7
- throw new Error("Invalid config: apps must be an array");
8
- }
9
- if (typeof config?.baseUrl !== "string" || !config.baseUrl) {
10
- throw new Error("Invalid config: baseUrl must be a non-empty string");
11
- }
12
- if (config.apps.length === 0) {
13
- throw new Error("Invalid config: apps array is empty");
14
- }
15
- const invalidApps = config.apps.filter((app) => !isCompleteConfig(app));
16
- if (invalidApps.length > 0) {
17
- throw new Error(
18
- `Invalid config: ${invalidApps.length} app(s) missing required appId or privateKey`
19
- );
6
+ if (!config?.apps?.length) {
7
+ throw new Error("No apps provided");
20
8
  }
21
9
  log(`Using ${config.apps.length} app configs`);
22
10
  const octokits = config.apps.map((app) => createOctokit(app, config.baseUrl));
@@ -28,9 +16,9 @@ async function getApp(config) {
28
16
  rateLimits.map((r) => `app[${r.appIndex}]: ${r.remaining}/${r.limit}`).join(", ")
29
17
  );
30
18
  const { bestIndex, rateLimit } = rateLimits.reduce(
31
- (acc, cur, i) => {
19
+ (acc, cur) => {
32
20
  if (cur.remaining > acc.rateLimit.remaining) {
33
- return { bestIndex: i, rateLimit: cur };
21
+ return { bestIndex: cur.appIndex, rateLimit: cur };
34
22
  }
35
23
  return acc;
36
24
  },
@@ -44,9 +32,6 @@ async function getApp(config) {
44
32
  );
45
33
  return octokits[bestIndex];
46
34
  }
47
- function isCompleteConfig(config) {
48
- return typeof config === "object" && config !== null && "appId" in config && "privateKey" in config && !!(config.appId && config.privateKey);
49
- }
50
35
  function decodePrivateKey(key) {
51
36
  if (key.startsWith("-----BEGIN ") && key.includes("-----END ")) {
52
37
  return key;
@@ -58,9 +43,9 @@ function createOctokit(appConfig, baseUrl) {
58
43
  authStrategy: createAppAuth,
59
44
  baseUrl,
60
45
  auth: {
61
- appId: appConfig.appId,
62
- installationId: appConfig.installationId,
63
- privateKey: decodePrivateKey(appConfig.privateKey)
46
+ ...appConfig,
47
+ // TODO: this type check can be removed once the issue will be fixed in @octokit/auth-app
48
+ privateKey: appConfig.privateKey && typeof appConfig.privateKey === "string" ? decodePrivateKey(appConfig.privateKey) : void 0
64
49
  }
65
50
  });
66
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octokit-load-balancer",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
4
  "description": "Load balance across multiple GitHub App instances, always picking the one with most available rate limit",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -44,11 +44,11 @@
44
44
  "url": "git+https://github.com/frankie303/octokit-load-balancer.git"
45
45
  },
46
46
  "peerDependencies": {
47
- "@octokit/auth-app": "^7.0.0",
47
+ "@octokit/auth-app": "^8.0.0",
48
48
  "octokit": "^5.0.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@octokit/auth-app": "^7.2.2",
51
+ "@octokit/auth-app": "^8.1.2",
52
52
  "@types/node": "^20.19.28",
53
53
  "@vitest/coverage-v8": "^4.0.17",
54
54
  "octokit": "^5.0.5",