@maschinenlesbar.org/pegel-online-cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,9 @@ export interface RawResponse {
9
9
  /**
10
10
  * Options for {@link RequestEngine} and the client. The numeric options must be
11
11
  * integers within their documented range; anything else (negative, fractional,
12
- * NaN, Infinity, too large) makes the constructor throw a PegelError.
12
+ * NaN, Infinity, too large) makes the constructor throw a PegelValidationError, as
13
+ * does a `transport` or `sleep` that is not a function and a `headers` value an HTTP
14
+ * header can't carry.
13
15
  */
14
16
  export interface EngineOptions {
15
17
  /** Base URL of the API. Defaults to https://www.pegelonline.wsv.de */
@@ -32,15 +34,16 @@ export interface EngineOptions {
32
34
  */
33
35
  timeoutMs?: number;
34
36
  /**
35
- * Number of automatic retries for transient (429/503) responses, 0..`MAX_RETRIES`
36
- * (10). Each waits the
37
- * response's `Retry-After` (up to `MAX_RETRY_AFTER_MS`; a longer one is not
38
- * retried), or else `retryDelayMs * attempt`.
37
+ * Number of automatic retries for transient (429/503) responses and reset
38
+ * connections (ECONNRESET, EPIPE, ECONNABORTED, undici's UND_ERR_SOCKET, anywhere in
39
+ * the error's `cause` chain; GET and HEAD only), 0..`MAX_RETRIES` (10). Each waits
40
+ * `retryDelayMs * attempt`, or longer when the response's `Retry-After` asks (up to
41
+ * `MAX_RETRY_AFTER_MS`; a longer one is not retried). Timeouts are not retried.
39
42
  */
40
43
  maxRetries?: number;
41
44
  /**
42
- * Base backoff between retries in milliseconds (grows linearly); used without a
43
- * Retry-After. At most `MAX_RETRY_AFTER_MS`.
45
+ * Base backoff between retries in milliseconds (grows linearly); the floor of every
46
+ * wait, Retry-After or not. At most `MAX_RETRY_AFTER_MS`.
44
47
  */
45
48
  retryDelayMs?: number;
46
49
  /**
@@ -62,8 +65,10 @@ export declare const MAX_RETRIES = 10;
62
65
  /**
63
66
  * Longest `Retry-After` the engine waits out before retrying a 429/503. When the
64
67
  * server asks for longer, the engine does not retry at all and surfaces the error at
65
- * once: retrying early would only land inside the window the server asked us to wait
66
- * out, and a hostile value must not stall the CLI.
68
+ * once, naming the requested wait (`PegelApiError.retryAfterMs`): retrying early would
69
+ * only land inside the window the server asked us to wait out, and a hostile value must
70
+ * not stall the CLI. A shorter `Retry-After` never makes a wait shorter than the normal
71
+ * backoff.
67
72
  */
68
73
  export declare const MAX_RETRY_AFTER_MS = 30000;
69
74
  /**
@@ -77,6 +82,13 @@ export declare const MAX_RETRY_AFTER_MS = 30000;
77
82
  * read `"1.5"` as a date in 2001 and retry at once.
78
83
  */
79
84
  export declare function parseRetryAfter(header: string | string[] | undefined, now?: number): number | undefined;
85
+ /**
86
+ * True for a PegelNetworkError caused by a reset or aborted connection, which the
87
+ * engine retries — whichever transport raised it (a Node error, fetch's TypeError with
88
+ * an undici cause). A refused connection, a DNS failure or a timeout is not transient
89
+ * in that sense and is not retried.
90
+ */
91
+ export declare function isTransientNetworkError(err: unknown): boolean;
80
92
  /**
81
93
  * Check a base URL against the library's rules (baseUrlProblem: no whitespace or
82
94
  * control characters, an absolute http(s) URL, no query or fragment) and return it
@@ -87,10 +99,9 @@ export declare function parseRetryAfter(header: string | string[] | undefined, n
87
99
  */
88
100
  export declare function validateBaseUrl(raw: string): string;
89
101
  export declare class RequestEngine {
90
- private readonly baseUrl;
102
+ #private;
91
103
  private readonly transport;
92
104
  private readonly userAgent;
93
- private readonly extraHeaders;
94
105
  private readonly timeoutMs;
95
106
  private readonly maxRetries;
96
107
  private readonly retryDelayMs;
@@ -101,7 +112,7 @@ export declare class RequestEngine {
101
112
  /**
102
113
  * Build a fully-qualified URL from a path and optional query parameters.
103
114
  *
104
- * Throws a PegelError for a path with a "." or ".." segment. The resource methods
115
+ * Throws a PegelValidationError for a path with a "." or ".." segment. The resource methods
105
116
  * put ids into the path with `encodeURIComponent`, which leaves those two
106
117
  * unchanged, and URL parsing then resolves them: `currentMeasurement("BONN", "..")`
107
118
  * would request `/stations/currentmeasurement.json` (a station of that name).
@@ -109,6 +120,25 @@ export declare class RequestEngine {
109
120
  * encodeURIComponent turns their "%" into "%25".)
110
121
  */
111
122
  buildUrl(path: string, query?: QueryParams): string;
123
+ /**
124
+ * `text` without the base URL's credentials: server text (an error body that echoes
125
+ * the request URL) and transport text (fetch's "Failed to fetch <url>") can carry them.
126
+ */
127
+ private scrub;
128
+ /**
129
+ * A transport failure as the `cause` of the error the engine raises: the original
130
+ * when its text carries no credentials, otherwise a copy with them scrubbed (message,
131
+ * `code` and the cause chain kept), so logging the error with its causes can't reveal
132
+ * the base URL's password.
133
+ */
134
+ private scrubCause;
135
+ /**
136
+ * Call the transport under the overall deadline (`timeoutMs`): the request gets an
137
+ * AbortSignal that fires at the deadline, and the call rejects then whether the
138
+ * transport stops or not — a custom transport (fetch, a node:http wrapper) that
139
+ * ignores `timeoutMs` can't hang the caller. A synchronous throw becomes a rejection.
140
+ */
141
+ private callTransport;
112
142
  /** Perform a request with Accept negotiation and transient-error retries. */
113
143
  request(method: string, path: string, options?: {
114
144
  query?: QueryParams;