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.
- package/dist/cli/index.js +125 -24
- package/dist/clients/base.d.ts +8 -3
- package/dist/clients/base.d.ts.map +1 -1
- package/dist/clients/bazarr.d.ts +1 -0
- package/dist/clients/bazarr.d.ts.map +1 -1
- package/dist/clients/bazarr.js +95 -3
- package/dist/clients/lidarr.d.ts +2 -1
- package/dist/clients/lidarr.d.ts.map +1 -1
- package/dist/clients/lidarr.js +102 -5
- package/dist/clients/prowlarr.d.ts +2 -1
- package/dist/clients/prowlarr.d.ts.map +1 -1
- package/dist/clients/prowlarr.js +102 -5
- package/dist/clients/qbittorrent.d.ts +1 -1
- package/dist/clients/qbittorrent.d.ts.map +1 -1
- package/dist/clients/qbittorrent.js +95 -6
- package/dist/clients/radarr.d.ts +2 -1
- package/dist/clients/radarr.d.ts.map +1 -1
- package/dist/clients/radarr.js +102 -5
- package/dist/clients/readarr.d.ts +2 -1
- package/dist/clients/readarr.d.ts.map +1 -1
- package/dist/clients/readarr.js +102 -5
- package/dist/clients/seerr.d.ts +1 -0
- package/dist/clients/seerr.d.ts.map +1 -1
- package/dist/clients/seerr.js +95 -3
- package/dist/clients/sonarr.d.ts +2 -1
- package/dist/clients/sonarr.d.ts.map +1 -1
- package/dist/clients/sonarr.js +102 -5
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/fetch.d.ts +23 -0
- package/dist/core/fetch.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/tsarr-2.10.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/tsarr-2.9.1.tgz +0 -0
package/dist/clients/lidarr.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2316
|
-
client
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/dist/clients/prowlarr.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1709
|
-
client
|
|
1805
|
+
constructor(config) {
|
|
1806
|
+
super(config, client);
|
|
1710
1807
|
}
|
|
1711
1808
|
async getIndexerStats() {
|
|
1712
1809
|
return getApiV1Indexerstats();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qbittorrent.d.ts","sourceRoot":"","sources":["../../src/clients/qbittorrent.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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})`);
|
package/dist/clients/radarr.d.ts
CHANGED
|
@@ -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
|
-
|
|
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":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"radarr.d.ts","sourceRoot":"","sources":["../../src/clients/radarr.ts"],"names":[],"mappings":"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"}
|
package/dist/clients/radarr.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2311
|
-
client
|
|
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
|
-
|
|
20
|
+
constructor(config: ServarrClientConfig);
|
|
20
21
|
/**
|
|
21
22
|
* Get all authors in the library
|
|
22
23
|
*/
|