tsarr 2.9.1 → 2.10.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 (39) hide show
  1. package/dist/cli/index.js +125 -24
  2. package/dist/clients/base.d.ts +8 -3
  3. package/dist/clients/base.d.ts.map +1 -1
  4. package/dist/clients/bazarr.d.ts +1 -0
  5. package/dist/clients/bazarr.d.ts.map +1 -1
  6. package/dist/clients/bazarr.js +95 -3
  7. package/dist/clients/lidarr.d.ts +2 -1
  8. package/dist/clients/lidarr.d.ts.map +1 -1
  9. package/dist/clients/lidarr.js +102 -5
  10. package/dist/clients/prowlarr.d.ts +2 -1
  11. package/dist/clients/prowlarr.d.ts.map +1 -1
  12. package/dist/clients/prowlarr.js +102 -5
  13. package/dist/clients/qbittorrent.d.ts +1 -1
  14. package/dist/clients/qbittorrent.d.ts.map +1 -1
  15. package/dist/clients/qbittorrent.js +95 -6
  16. package/dist/clients/radarr.d.ts +2 -1
  17. package/dist/clients/radarr.d.ts.map +1 -1
  18. package/dist/clients/radarr.js +102 -5
  19. package/dist/clients/readarr.d.ts +2 -1
  20. package/dist/clients/readarr.d.ts.map +1 -1
  21. package/dist/clients/readarr.js +102 -5
  22. package/dist/clients/seerr.d.ts +1 -0
  23. package/dist/clients/seerr.d.ts.map +1 -1
  24. package/dist/clients/seerr.js +95 -3
  25. package/dist/clients/sonarr.d.ts +2 -1
  26. package/dist/clients/sonarr.d.ts.map +1 -1
  27. package/dist/clients/sonarr.js +102 -5
  28. package/dist/core/client.d.ts +2 -0
  29. package/dist/core/client.d.ts.map +1 -1
  30. package/dist/core/fetch.d.ts +23 -0
  31. package/dist/core/fetch.d.ts.map +1 -0
  32. package/dist/core/index.d.ts +1 -0
  33. package/dist/core/index.d.ts.map +1 -1
  34. package/dist/core/types.d.ts +7 -0
  35. package/dist/core/types.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/tsarr-2.10.0.tgz +0 -0
  38. package/package.json +1 -1
  39. package/dist/tsarr-2.9.1.tgz +0 -0
@@ -26,6 +26,93 @@ class ConnectionError extends TsarrError {
26
26
  }
27
27
  }
28
28
 
29
+ // src/core/fetch.ts
30
+ var DEFAULT_TIMEOUT = 30000;
31
+ var DEFAULT_MAX_RETRIES = 3;
32
+ var DEFAULT_INITIAL_DELAY = 1000;
33
+ var DEFAULT_MAX_DELAY = 1e4;
34
+ var RETRYABLE_STATUS_CODES = new Set([408, 429, 502, 503, 504]);
35
+ function isRetryable(error) {
36
+ if (error instanceof DOMException && error.name === "AbortError") {
37
+ return false;
38
+ }
39
+ if (error instanceof TypeError) {
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ function getRetryDelay(attempt, initialDelayMs, maxDelayMs) {
45
+ const delay = initialDelayMs * 2 ** attempt;
46
+ const jitter = delay * 0.2 * Math.random();
47
+ return Math.min(delay + jitter, maxDelayMs);
48
+ }
49
+ function createResilientFetch(options = {}) {
50
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
51
+ const maxRetries = options.retry ? options.retry.maxRetries ?? DEFAULT_MAX_RETRIES : 0;
52
+ const initialDelayMs = options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY;
53
+ const maxDelayMs = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY;
54
+ const resilientFetch = async (input, init) => {
55
+ let lastError;
56
+ const template = createRequestTemplate(input, init);
57
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
58
+ const controller = new AbortController;
59
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
60
+ const callerSignal = init?.signal;
61
+ if (callerSignal?.aborted) {
62
+ clearTimeout(timeoutId);
63
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
64
+ }
65
+ const onCallerAbort = () => controller.abort(callerSignal.reason);
66
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
67
+ try {
68
+ const response = await globalThis.fetch(new Request(template.clone(), { signal: controller.signal }));
69
+ clearTimeout(timeoutId);
70
+ callerSignal?.removeEventListener("abort", onCallerAbort);
71
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
72
+ lastError = new ConnectionError(`Request failed with status ${response.status}`);
73
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
74
+ await new Promise((resolve) => setTimeout(resolve, delay));
75
+ continue;
76
+ }
77
+ return response;
78
+ } catch (error) {
79
+ clearTimeout(timeoutId);
80
+ callerSignal?.removeEventListener("abort", onCallerAbort);
81
+ if (callerSignal?.aborted) {
82
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
83
+ }
84
+ if (error instanceof DOMException && error.name === "AbortError") {
85
+ lastError = new ConnectionError(`Request timed out after ${timeout}ms`);
86
+ if (attempt < maxRetries) {
87
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
88
+ await new Promise((resolve) => setTimeout(resolve, delay));
89
+ continue;
90
+ }
91
+ throw lastError;
92
+ }
93
+ if (isRetryable(error) && attempt < maxRetries) {
94
+ lastError = error;
95
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
96
+ await new Promise((resolve) => setTimeout(resolve, delay));
97
+ continue;
98
+ }
99
+ throw error;
100
+ }
101
+ }
102
+ throw lastError;
103
+ };
104
+ return Object.assign(resilientFetch, {
105
+ preconnect: globalThis.fetch.preconnect?.bind(globalThis.fetch)
106
+ });
107
+ }
108
+ function createRequestTemplate(input, init) {
109
+ const { signal: _signal, ...requestInit } = init ?? {};
110
+ if (input instanceof Request) {
111
+ return init ? new Request(input.clone(), requestInit) : input.clone();
112
+ }
113
+ return new Request(input, requestInit);
114
+ }
115
+
29
116
  // src/core/client.ts
30
117
  var DEFAULT_TIMEOUT_MS = 30000;
31
118
  function createServarrClient(config) {
@@ -40,6 +127,10 @@ function createServarrClient(config) {
40
127
  baseUrl: config.baseUrl.replace(/\/$/, "")
41
128
  };
42
129
  const timeoutMs = validatedConfig.timeout ?? DEFAULT_TIMEOUT_MS;
130
+ const resilientFetch = createResilientFetch({
131
+ timeout: timeoutMs,
132
+ retry: validatedConfig.retry
133
+ });
43
134
  return {
44
135
  config: validatedConfig,
45
136
  getHeaders: () => ({
@@ -48,22 +139,28 @@ function createServarrClient(config) {
48
139
  ...validatedConfig.headers
49
140
  }),
50
141
  getBaseUrl: () => validatedConfig.baseUrl,
51
- getTimeout: () => timeoutMs
142
+ getTimeout: () => timeoutMs,
143
+ getFetch: () => resilientFetch
52
144
  };
53
145
  }
54
146
 
55
147
  // src/clients/base.ts
56
148
  class ServarrBaseClient {
57
149
  clientConfig;
58
- constructor(config) {
150
+ rawClient;
151
+ constructor(config, rawClient) {
152
+ this.rawClient = rawClient;
59
153
  this.clientConfig = createServarrClient(config);
60
154
  this.configureRawClient();
61
155
  }
156
+ configureRawClient() {
157
+ this.rawClient.setConfig(this.getClientConfig());
158
+ }
62
159
  getClientConfig() {
63
160
  return {
64
161
  baseUrl: this.clientConfig.getBaseUrl(),
65
162
  headers: this.clientConfig.getHeaders(),
66
- signal: AbortSignal.timeout(this.clientConfig.getTimeout())
163
+ fetch: this.clientConfig.getFetch()
67
164
  };
68
165
  }
69
166
  async getSystemStatus() {
@@ -2312,8 +2409,8 @@ class LidarrClient extends ServarrBaseClient {
2312
2409
  getUiConfigById: getApiV1ConfigUiById,
2313
2410
  updateUiConfig: putApiV1ConfigUiById
2314
2411
  };
2315
- configureRawClient() {
2316
- client.setConfig(this.getClientConfig());
2412
+ constructor(config) {
2413
+ super(config, client);
2317
2414
  }
2318
2415
  async getArtists() {
2319
2416
  return getApiV1Artist();
@@ -1,4 +1,5 @@
1
1
  import { ServarrBaseClient, type ServarrOps } from '../clients/base';
2
+ import type { ServarrClientConfig } from '../core/types';
2
3
  import * as ProwlarrApi from '../generated/prowlarr/index';
3
4
  import type { ApplicationResource, DevelopmentConfigResource } from '../generated/prowlarr/types.gen';
4
5
  /**
@@ -16,7 +17,7 @@ import type { ApplicationResource, DevelopmentConfigResource } from '../generate
16
17
  */
17
18
  export declare class ProwlarrClient extends ServarrBaseClient {
18
19
  protected readonly ops: ServarrOps;
19
- protected configureRawClient(): void;
20
+ constructor(config: ServarrClientConfig);
20
21
  /**
21
22
  * Get indexer statistics
22
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"prowlarr.d.ts","sourceRoot":"","sources":["../../src/clients/prowlarr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAErE,OAAO,KAAK,WAAW,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,iCAAiC,CAAC;AAEzC;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,SAAQ,iBAAiB;IACnD,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAmEhC;IAEF,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAQpC;;OAEG;IACG,eAAe;;;;;;;;;;IAMrB;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE;;;;;;;;;;IAWjD;;OAEG;IACG,eAAe;;;;;;;;;;IAIrB;;OAEG;IACG,cAAc,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;IAI/B;;OAEG;IACG,cAAc,CAAC,WAAW,EAAE,mBAAmB;;;;;;;;;;IAIrD;;OAEG;IACG,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB;;;;;;;;;;IAIpE;;OAEG;IACG,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;IAIlC;;OAEG;IACG,eAAe,CAAC,WAAW,EAAE,mBAAmB;;;;;;;;;;IAItD;;OAEG;IACG,mBAAmB;;;;;;;;;;IAIzB;;OAEG;IACG,oBAAoB;;;;;;;;;;IAM1B;;OAEG;IACG,oBAAoB;;;;;;;;;;IAI1B;;OAEG;IACG,wBAAwB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;IAIzC;;OAEG;IACG,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,yBAAyB;;;;;;;;;;IAM3E;;OAEG;IACG,aAAa;;;;;;;;;;CAGpB;AAGD,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"prowlarr.d.ts","sourceRoot":"","sources":["../../src/clients/prowlarr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,KAAK,WAAW,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,iCAAiC,CAAC;AAEzC;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,SAAQ,iBAAiB;IACnD,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAmEhC;gBAEU,MAAM,EAAE,mBAAmB;IAQvC;;OAEG;IACG,eAAe;;;;;;;;;;IAMrB;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE;;;;;;;;;;IAWjD;;OAEG;IACG,eAAe;;;;;;;;;;IAIrB;;OAEG;IACG,cAAc,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;IAI/B;;OAEG;IACG,cAAc,CAAC,WAAW,EAAE,mBAAmB;;;;;;;;;;IAIrD;;OAEG;IACG,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB;;;;;;;;;;IAIpE;;OAEG;IACG,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;IAIlC;;OAEG;IACG,eAAe,CAAC,WAAW,EAAE,mBAAmB;;;;;;;;;;IAItD;;OAEG;IACG,mBAAmB;;;;;;;;;;IAIzB;;OAEG;IACG,oBAAoB;;;;;;;;;;IAM1B;;OAEG;IACG,oBAAoB;;;;;;;;;;IAI1B;;OAEG;IACG,wBAAwB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;IAIzC;;OAEG;IACG,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,yBAAyB;;;;;;;;;;IAM3E;;OAEG;IACG,aAAa;;;;;;;;;;CAGpB;AAGD,cAAc,qBAAqB,CAAC"}
@@ -26,6 +26,93 @@ class ConnectionError extends TsarrError {
26
26
  }
27
27
  }
28
28
 
29
+ // src/core/fetch.ts
30
+ var DEFAULT_TIMEOUT = 30000;
31
+ var DEFAULT_MAX_RETRIES = 3;
32
+ var DEFAULT_INITIAL_DELAY = 1000;
33
+ var DEFAULT_MAX_DELAY = 1e4;
34
+ var RETRYABLE_STATUS_CODES = new Set([408, 429, 502, 503, 504]);
35
+ function isRetryable(error) {
36
+ if (error instanceof DOMException && error.name === "AbortError") {
37
+ return false;
38
+ }
39
+ if (error instanceof TypeError) {
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ function getRetryDelay(attempt, initialDelayMs, maxDelayMs) {
45
+ const delay = initialDelayMs * 2 ** attempt;
46
+ const jitter = delay * 0.2 * Math.random();
47
+ return Math.min(delay + jitter, maxDelayMs);
48
+ }
49
+ function createResilientFetch(options = {}) {
50
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
51
+ const maxRetries = options.retry ? options.retry.maxRetries ?? DEFAULT_MAX_RETRIES : 0;
52
+ const initialDelayMs = options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY;
53
+ const maxDelayMs = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY;
54
+ const resilientFetch = async (input, init) => {
55
+ let lastError;
56
+ const template = createRequestTemplate(input, init);
57
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
58
+ const controller = new AbortController;
59
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
60
+ const callerSignal = init?.signal;
61
+ if (callerSignal?.aborted) {
62
+ clearTimeout(timeoutId);
63
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
64
+ }
65
+ const onCallerAbort = () => controller.abort(callerSignal.reason);
66
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
67
+ try {
68
+ const response = await globalThis.fetch(new Request(template.clone(), { signal: controller.signal }));
69
+ clearTimeout(timeoutId);
70
+ callerSignal?.removeEventListener("abort", onCallerAbort);
71
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
72
+ lastError = new ConnectionError(`Request failed with status ${response.status}`);
73
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
74
+ await new Promise((resolve) => setTimeout(resolve, delay));
75
+ continue;
76
+ }
77
+ return response;
78
+ } catch (error) {
79
+ clearTimeout(timeoutId);
80
+ callerSignal?.removeEventListener("abort", onCallerAbort);
81
+ if (callerSignal?.aborted) {
82
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
83
+ }
84
+ if (error instanceof DOMException && error.name === "AbortError") {
85
+ lastError = new ConnectionError(`Request timed out after ${timeout}ms`);
86
+ if (attempt < maxRetries) {
87
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
88
+ await new Promise((resolve) => setTimeout(resolve, delay));
89
+ continue;
90
+ }
91
+ throw lastError;
92
+ }
93
+ if (isRetryable(error) && attempt < maxRetries) {
94
+ lastError = error;
95
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
96
+ await new Promise((resolve) => setTimeout(resolve, delay));
97
+ continue;
98
+ }
99
+ throw error;
100
+ }
101
+ }
102
+ throw lastError;
103
+ };
104
+ return Object.assign(resilientFetch, {
105
+ preconnect: globalThis.fetch.preconnect?.bind(globalThis.fetch)
106
+ });
107
+ }
108
+ function createRequestTemplate(input, init) {
109
+ const { signal: _signal, ...requestInit } = init ?? {};
110
+ if (input instanceof Request) {
111
+ return init ? new Request(input.clone(), requestInit) : input.clone();
112
+ }
113
+ return new Request(input, requestInit);
114
+ }
115
+
29
116
  // src/core/client.ts
30
117
  var DEFAULT_TIMEOUT_MS = 30000;
31
118
  function createServarrClient(config) {
@@ -40,6 +127,10 @@ function createServarrClient(config) {
40
127
  baseUrl: config.baseUrl.replace(/\/$/, "")
41
128
  };
42
129
  const timeoutMs = validatedConfig.timeout ?? DEFAULT_TIMEOUT_MS;
130
+ const resilientFetch = createResilientFetch({
131
+ timeout: timeoutMs,
132
+ retry: validatedConfig.retry
133
+ });
43
134
  return {
44
135
  config: validatedConfig,
45
136
  getHeaders: () => ({
@@ -48,22 +139,28 @@ function createServarrClient(config) {
48
139
  ...validatedConfig.headers
49
140
  }),
50
141
  getBaseUrl: () => validatedConfig.baseUrl,
51
- getTimeout: () => timeoutMs
142
+ getTimeout: () => timeoutMs,
143
+ getFetch: () => resilientFetch
52
144
  };
53
145
  }
54
146
 
55
147
  // src/clients/base.ts
56
148
  class ServarrBaseClient {
57
149
  clientConfig;
58
- constructor(config) {
150
+ rawClient;
151
+ constructor(config, rawClient) {
152
+ this.rawClient = rawClient;
59
153
  this.clientConfig = createServarrClient(config);
60
154
  this.configureRawClient();
61
155
  }
156
+ configureRawClient() {
157
+ this.rawClient.setConfig(this.getClientConfig());
158
+ }
62
159
  getClientConfig() {
63
160
  return {
64
161
  baseUrl: this.clientConfig.getBaseUrl(),
65
162
  headers: this.clientConfig.getHeaders(),
66
- signal: AbortSignal.timeout(this.clientConfig.getTimeout())
163
+ fetch: this.clientConfig.getFetch()
67
164
  };
68
165
  }
69
166
  async getSystemStatus() {
@@ -1705,8 +1802,8 @@ class ProwlarrClient extends ServarrBaseClient {
1705
1802
  getUiConfigById: getApiV1ConfigUiById,
1706
1803
  updateUiConfig: putApiV1ConfigUiById
1707
1804
  };
1708
- configureRawClient() {
1709
- client.setConfig(this.getClientConfig());
1805
+ constructor(config) {
1806
+ super(config, client);
1710
1807
  }
1711
1808
  async getIndexerStats() {
1712
1809
  return getApiV1Indexerstats();
@@ -6,7 +6,7 @@ export declare class QBittorrentClient {
6
6
  private username;
7
7
  private password;
8
8
  private sid;
9
- private timeoutMs;
9
+ private fetch;
10
10
  constructor(config: QBittorrentClientConfig);
11
11
  private ensureAuth;
12
12
  private login;
@@ -1 +1 @@
1
- {"version":3,"file":"qbittorrent.d.ts","sourceRoot":"","sources":["../../src/clients/qbittorrent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAG7D,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,YAAY,EACb,MAAM,oCAAoC,CAAC;AAE5C,KAAK,aAAa,GAAG,WAAW,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAoBzE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,GAAG,CAAuB;IAClC,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,uBAAuB;YAiB7B,UAAU;YAOV,KAAK;IAkCb,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAKhC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAKhC,eAAe;;;IAOf,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;IAOxC,WAAW,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAS3D,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM7C,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAKzE;AAGD,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"qbittorrent.d.ts","sourceRoot":"","sources":["../../src/clients/qbittorrent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAG7D,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,YAAY,EACb,MAAM,oCAAoC,CAAC;AAE5C,KAAK,aAAa,GAAG,WAAW,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAoBzE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,GAAG,CAAuB;IAClC,OAAO,CAAC,KAAK,CAA0B;gBAE3B,MAAM,EAAE,uBAAuB;YAoB7B,UAAU;YAOV,KAAK;IAiCb,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAKhC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAKhC,eAAe;;;IAOf,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;IAOxC,WAAW,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAS3D,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM7C,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAKzE;AAGD,cAAc,wBAAwB,CAAC"}
@@ -18,6 +18,93 @@ class ConnectionError extends TsarrError {
18
18
  }
19
19
  }
20
20
 
21
+ // src/core/fetch.ts
22
+ var DEFAULT_TIMEOUT = 30000;
23
+ var DEFAULT_MAX_RETRIES = 3;
24
+ var DEFAULT_INITIAL_DELAY = 1000;
25
+ var DEFAULT_MAX_DELAY = 1e4;
26
+ var RETRYABLE_STATUS_CODES = new Set([408, 429, 502, 503, 504]);
27
+ function isRetryable(error) {
28
+ if (error instanceof DOMException && error.name === "AbortError") {
29
+ return false;
30
+ }
31
+ if (error instanceof TypeError) {
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ function getRetryDelay(attempt, initialDelayMs, maxDelayMs) {
37
+ const delay = initialDelayMs * 2 ** attempt;
38
+ const jitter = delay * 0.2 * Math.random();
39
+ return Math.min(delay + jitter, maxDelayMs);
40
+ }
41
+ function createResilientFetch(options = {}) {
42
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
43
+ const maxRetries = options.retry ? options.retry.maxRetries ?? DEFAULT_MAX_RETRIES : 0;
44
+ const initialDelayMs = options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY;
45
+ const maxDelayMs = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY;
46
+ const resilientFetch = async (input, init) => {
47
+ let lastError;
48
+ const template = createRequestTemplate(input, init);
49
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
50
+ const controller = new AbortController;
51
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
52
+ const callerSignal = init?.signal;
53
+ if (callerSignal?.aborted) {
54
+ clearTimeout(timeoutId);
55
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
56
+ }
57
+ const onCallerAbort = () => controller.abort(callerSignal.reason);
58
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
59
+ try {
60
+ const response = await globalThis.fetch(new Request(template.clone(), { signal: controller.signal }));
61
+ clearTimeout(timeoutId);
62
+ callerSignal?.removeEventListener("abort", onCallerAbort);
63
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
64
+ lastError = new ConnectionError(`Request failed with status ${response.status}`);
65
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
66
+ await new Promise((resolve) => setTimeout(resolve, delay));
67
+ continue;
68
+ }
69
+ return response;
70
+ } catch (error) {
71
+ clearTimeout(timeoutId);
72
+ callerSignal?.removeEventListener("abort", onCallerAbort);
73
+ if (callerSignal?.aborted) {
74
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
75
+ }
76
+ if (error instanceof DOMException && error.name === "AbortError") {
77
+ lastError = new ConnectionError(`Request timed out after ${timeout}ms`);
78
+ if (attempt < maxRetries) {
79
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
80
+ await new Promise((resolve) => setTimeout(resolve, delay));
81
+ continue;
82
+ }
83
+ throw lastError;
84
+ }
85
+ if (isRetryable(error) && attempt < maxRetries) {
86
+ lastError = error;
87
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
88
+ await new Promise((resolve) => setTimeout(resolve, delay));
89
+ continue;
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+ throw lastError;
95
+ };
96
+ return Object.assign(resilientFetch, {
97
+ preconnect: globalThis.fetch.preconnect?.bind(globalThis.fetch)
98
+ });
99
+ }
100
+ function createRequestTemplate(input, init) {
101
+ const { signal: _signal, ...requestInit } = init ?? {};
102
+ if (input instanceof Request) {
103
+ return init ? new Request(input.clone(), requestInit) : input.clone();
104
+ }
105
+ return new Request(input, requestInit);
106
+ }
107
+
21
108
  // src/generated/qbittorrent/core/bodySerializer.gen.ts
22
109
  var serializeUrlSearchParamsPair = (data, key, value) => {
23
110
  if (typeof value === "string") {
@@ -924,7 +1011,7 @@ class QBittorrentClient {
924
1011
  username;
925
1012
  password;
926
1013
  sid = null;
927
- timeoutMs;
1014
+ fetch;
928
1015
  constructor(config) {
929
1016
  if (!config.baseUrl) {
930
1017
  throw new ConnectionError("No base URL provided");
@@ -932,11 +1019,14 @@ class QBittorrentClient {
932
1019
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
933
1020
  this.username = config.username;
934
1021
  this.password = config.password;
935
- this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
1022
+ this.fetch = createResilientFetch({
1023
+ timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
1024
+ retry: config.retry
1025
+ });
936
1026
  client.setConfig({
937
1027
  baseUrl: `${this.baseUrl}/api/v2`,
938
1028
  auth: () => this.ensureAuth(),
939
- signal: AbortSignal.timeout(this.timeoutMs)
1029
+ fetch: this.fetch
940
1030
  });
941
1031
  }
942
1032
  async ensureAuth() {
@@ -946,7 +1036,7 @@ class QBittorrentClient {
946
1036
  return this.sid;
947
1037
  }
948
1038
  async login() {
949
- const response = await fetch(`${this.baseUrl}/api/v2/auth/login`, {
1039
+ const response = await this.fetch(`${this.baseUrl}/api/v2/auth/login`, {
950
1040
  method: "POST",
951
1041
  headers: {
952
1042
  "Content-Type": "application/x-www-form-urlencoded",
@@ -955,8 +1045,7 @@ class QBittorrentClient {
955
1045
  body: new URLSearchParams({
956
1046
  username: this.username,
957
1047
  password: this.password
958
- }),
959
- signal: AbortSignal.timeout(this.timeoutMs)
1048
+ })
960
1049
  });
961
1050
  if (!response.ok) {
962
1051
  throw new ConnectionError(`qBittorrent login failed (${response.status})`);
@@ -1,9 +1,10 @@
1
1
  import { ServarrBaseClient, type ServarrOps } from '../clients/base';
2
+ import type { ServarrClientConfig } from '../core/types';
2
3
  import * as RadarrApi from '../generated/radarr/index';
3
4
  import type { CustomFormatBulkResource, CustomFormatResource, DownloadClientBulkResource, ImportListResource, MediaManagementConfigResource, MovieFileListResource, MovieFileResource, MovieResource, NamingConfigResource, QualityProfileResource } from '../generated/radarr/types.gen';
4
5
  export declare class RadarrClient extends ServarrBaseClient {
5
6
  protected readonly ops: ServarrOps;
6
- protected configureRawClient(): void;
7
+ constructor(config: ServarrClientConfig);
7
8
  /**
8
9
  * Get all movies in the library
9
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"radarr.d.ts","sourceRoot":"","sources":["../../src/clients/radarr.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"radarr.d.ts","sourceRoot":"","sources":["../../src/clients/radarr.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,93 @@ class ConnectionError extends TsarrError {
26
26
  }
27
27
  }
28
28
 
29
+ // src/core/fetch.ts
30
+ var DEFAULT_TIMEOUT = 30000;
31
+ var DEFAULT_MAX_RETRIES = 3;
32
+ var DEFAULT_INITIAL_DELAY = 1000;
33
+ var DEFAULT_MAX_DELAY = 1e4;
34
+ var RETRYABLE_STATUS_CODES = new Set([408, 429, 502, 503, 504]);
35
+ function isRetryable(error) {
36
+ if (error instanceof DOMException && error.name === "AbortError") {
37
+ return false;
38
+ }
39
+ if (error instanceof TypeError) {
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ function getRetryDelay(attempt, initialDelayMs, maxDelayMs) {
45
+ const delay = initialDelayMs * 2 ** attempt;
46
+ const jitter = delay * 0.2 * Math.random();
47
+ return Math.min(delay + jitter, maxDelayMs);
48
+ }
49
+ function createResilientFetch(options = {}) {
50
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
51
+ const maxRetries = options.retry ? options.retry.maxRetries ?? DEFAULT_MAX_RETRIES : 0;
52
+ const initialDelayMs = options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY;
53
+ const maxDelayMs = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY;
54
+ const resilientFetch = async (input, init) => {
55
+ let lastError;
56
+ const template = createRequestTemplate(input, init);
57
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
58
+ const controller = new AbortController;
59
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
60
+ const callerSignal = init?.signal;
61
+ if (callerSignal?.aborted) {
62
+ clearTimeout(timeoutId);
63
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
64
+ }
65
+ const onCallerAbort = () => controller.abort(callerSignal.reason);
66
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
67
+ try {
68
+ const response = await globalThis.fetch(new Request(template.clone(), { signal: controller.signal }));
69
+ clearTimeout(timeoutId);
70
+ callerSignal?.removeEventListener("abort", onCallerAbort);
71
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
72
+ lastError = new ConnectionError(`Request failed with status ${response.status}`);
73
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
74
+ await new Promise((resolve) => setTimeout(resolve, delay));
75
+ continue;
76
+ }
77
+ return response;
78
+ } catch (error) {
79
+ clearTimeout(timeoutId);
80
+ callerSignal?.removeEventListener("abort", onCallerAbort);
81
+ if (callerSignal?.aborted) {
82
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
83
+ }
84
+ if (error instanceof DOMException && error.name === "AbortError") {
85
+ lastError = new ConnectionError(`Request timed out after ${timeout}ms`);
86
+ if (attempt < maxRetries) {
87
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
88
+ await new Promise((resolve) => setTimeout(resolve, delay));
89
+ continue;
90
+ }
91
+ throw lastError;
92
+ }
93
+ if (isRetryable(error) && attempt < maxRetries) {
94
+ lastError = error;
95
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
96
+ await new Promise((resolve) => setTimeout(resolve, delay));
97
+ continue;
98
+ }
99
+ throw error;
100
+ }
101
+ }
102
+ throw lastError;
103
+ };
104
+ return Object.assign(resilientFetch, {
105
+ preconnect: globalThis.fetch.preconnect?.bind(globalThis.fetch)
106
+ });
107
+ }
108
+ function createRequestTemplate(input, init) {
109
+ const { signal: _signal, ...requestInit } = init ?? {};
110
+ if (input instanceof Request) {
111
+ return init ? new Request(input.clone(), requestInit) : input.clone();
112
+ }
113
+ return new Request(input, requestInit);
114
+ }
115
+
29
116
  // src/core/client.ts
30
117
  var DEFAULT_TIMEOUT_MS = 30000;
31
118
  function createServarrClient(config) {
@@ -40,6 +127,10 @@ function createServarrClient(config) {
40
127
  baseUrl: config.baseUrl.replace(/\/$/, "")
41
128
  };
42
129
  const timeoutMs = validatedConfig.timeout ?? DEFAULT_TIMEOUT_MS;
130
+ const resilientFetch = createResilientFetch({
131
+ timeout: timeoutMs,
132
+ retry: validatedConfig.retry
133
+ });
43
134
  return {
44
135
  config: validatedConfig,
45
136
  getHeaders: () => ({
@@ -48,22 +139,28 @@ function createServarrClient(config) {
48
139
  ...validatedConfig.headers
49
140
  }),
50
141
  getBaseUrl: () => validatedConfig.baseUrl,
51
- getTimeout: () => timeoutMs
142
+ getTimeout: () => timeoutMs,
143
+ getFetch: () => resilientFetch
52
144
  };
53
145
  }
54
146
 
55
147
  // src/clients/base.ts
56
148
  class ServarrBaseClient {
57
149
  clientConfig;
58
- constructor(config) {
150
+ rawClient;
151
+ constructor(config, rawClient) {
152
+ this.rawClient = rawClient;
59
153
  this.clientConfig = createServarrClient(config);
60
154
  this.configureRawClient();
61
155
  }
156
+ configureRawClient() {
157
+ this.rawClient.setConfig(this.getClientConfig());
158
+ }
62
159
  getClientConfig() {
63
160
  return {
64
161
  baseUrl: this.clientConfig.getBaseUrl(),
65
162
  headers: this.clientConfig.getHeaders(),
66
- signal: AbortSignal.timeout(this.clientConfig.getTimeout())
163
+ fetch: this.clientConfig.getFetch()
67
164
  };
68
165
  }
69
166
  async getSystemStatus() {
@@ -2307,8 +2404,8 @@ class RadarrClient extends ServarrBaseClient {
2307
2404
  getUiConfigById: getApiV3ConfigUiById,
2308
2405
  updateUiConfig: putApiV3ConfigUiById
2309
2406
  };
2310
- configureRawClient() {
2311
- client.setConfig(this.getClientConfig());
2407
+ constructor(config) {
2408
+ super(config, client);
2312
2409
  }
2313
2410
  async getMovies() {
2314
2411
  return getApiV3Movie();
@@ -1,4 +1,5 @@
1
1
  import { ServarrBaseClient, type ServarrOps } from '../clients/base';
2
+ import type { ServarrClientConfig } from '../core/types';
2
3
  import * as ReadarrApi from '../generated/readarr/index';
3
4
  import type { AuthorResourceWritable, BookFileListResource, BookFileResourceWritable, BookResource, CustomFormatResource, DevelopmentConfigResource, ImportListResource, MediaManagementConfigResource, MetadataProviderConfigResource, NamingConfigResource, QualityProfileResource } from '../generated/readarr/types.gen';
4
5
  /**
@@ -16,7 +17,7 @@ import type { AuthorResourceWritable, BookFileListResource, BookFileResourceWrit
16
17
  */
17
18
  export declare class ReadarrClient extends ServarrBaseClient {
18
19
  protected readonly ops: ServarrOps;
19
- protected configureRawClient(): void;
20
+ constructor(config: ServarrClientConfig);
20
21
  /**
21
22
  * Get all authors in the library
22
23
  */