@lumavox/server 0.2.1 → 0.2.2
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/errors.js +2 -1
- package/dist/transport.d.ts +7 -3
- package/dist/transport.js +37 -16
- package/package.json +1 -1
package/dist/errors.js
CHANGED
|
@@ -17,7 +17,8 @@ export class PlatformError extends Error {
|
|
|
17
17
|
// Written out, not `new.target.name`: a customer's minifier renames classes.
|
|
18
18
|
name = "PlatformError";
|
|
19
19
|
constructor(init) {
|
|
20
|
-
|
|
20
|
+
// `init` is the options too: `Error` reads only its `cause`.
|
|
21
|
+
super(init.message, init);
|
|
21
22
|
this.status = init.status;
|
|
22
23
|
this.type = init.type;
|
|
23
24
|
this.requestId = init.requestId;
|
package/dist/transport.d.ts
CHANGED
|
@@ -49,9 +49,13 @@ export interface Answer<T> {
|
|
|
49
49
|
}
|
|
50
50
|
/** The longest `Retry-After` slept through; past it the 429 is thrown. */
|
|
51
51
|
export declare const MAX_RETRY_AFTER_S = 60;
|
|
52
|
-
/** A path parameter, encoded: an `external_id` may hold `/` or `?`. */
|
|
53
|
-
export declare const segment: (value: string) => string;
|
|
54
|
-
/**
|
|
52
|
+
/** A path parameter or a query's value, encoded: an `external_id` may hold `/` or `?`. */
|
|
53
|
+
export declare const segment: (value: string | number | boolean) => string;
|
|
54
|
+
/**
|
|
55
|
+
* A request id of the API's own shape: 16 hex. From `Math.random`, not
|
|
56
|
+
* `crypto`, which React Native lacks (this file is the client SDK's too):
|
|
57
|
+
* an id names a call in the logs and is no secret.
|
|
58
|
+
*/
|
|
55
59
|
export declare const newRequestId: () => string;
|
|
56
60
|
/**
|
|
57
61
|
* How long to wait before retry `attempt` (1, 2, …): the doubling backoff
|
package/dist/transport.js
CHANGED
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
* wins, up to a minute. A longer one is thrown at once as a
|
|
27
27
|
* `RateLimitError` with its `retryAfter`: a call that sleeps for an hour
|
|
28
28
|
* is not one a caller can reason about.
|
|
29
|
+
* - **The client SDK runs this file too** (`sdks/client/src/transport.ts`,
|
|
30
|
+
* a copy its `test/copies.test.ts` holds identical), on React Native's
|
|
31
|
+
* Hermes as well: so no `crypto`, `URLSearchParams`, `AbortSignal.any`
|
|
32
|
+
* or global `ReadableStream` taken for granted, each of which it lacks.
|
|
29
33
|
*/
|
|
30
34
|
import { ConnectionError, errorFromResponse, NotFoundError, PlatformError, TimeoutError, } from "./errors.js";
|
|
31
35
|
const RETRIED_STATUS = new Set([429, 502, 503, 504]);
|
|
@@ -33,10 +37,14 @@ const BASE_DELAY_MS = 500;
|
|
|
33
37
|
const MAX_DELAY_MS = 8_000;
|
|
34
38
|
/** The longest `Retry-After` slept through; past it the 429 is thrown. */
|
|
35
39
|
export const MAX_RETRY_AFTER_S = 60;
|
|
36
|
-
/** A path parameter, encoded: an `external_id` may hold `/` or `?`. */
|
|
40
|
+
/** A path parameter or a query's value, encoded: an `external_id` may hold `/` or `?`. */
|
|
37
41
|
export const segment = (value) => encodeURIComponent(value);
|
|
38
|
-
/**
|
|
39
|
-
|
|
42
|
+
/**
|
|
43
|
+
* A request id of the API's own shape: 16 hex. From `Math.random`, not
|
|
44
|
+
* `crypto`, which React Native lacks (this file is the client SDK's too):
|
|
45
|
+
* an id names a call in the logs and is no secret.
|
|
46
|
+
*/
|
|
47
|
+
export const newRequestId = () => "x".repeat(16).replace(/x/g, () => ((Math.random() * 16) | 0).toString(16));
|
|
40
48
|
/**
|
|
41
49
|
* How long to wait before retry `attempt` (1, 2, …): the doubling backoff
|
|
42
50
|
* less up to a quarter, or `Retry-After`'s seconds when the API sent one.
|
|
@@ -101,7 +109,8 @@ export class Transport {
|
|
|
101
109
|
}
|
|
102
110
|
async request(call, options = {}) {
|
|
103
111
|
const requestId = options.requestId ?? newRequestId();
|
|
104
|
-
|
|
112
|
+
// A stream by its shape: not every runtime has a global `ReadableStream`.
|
|
113
|
+
const once = "getReader" in Object(call.raw);
|
|
105
114
|
const maxRetries = once
|
|
106
115
|
? 0
|
|
107
116
|
: (options.maxRetries ?? this.config.maxRetries);
|
|
@@ -170,27 +179,37 @@ export class Transport {
|
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
url(call) {
|
|
173
|
-
|
|
182
|
+
// By hand: React Native's `URLSearchParams` is partial.
|
|
183
|
+
let query = "";
|
|
174
184
|
for (const [key, value] of Object.entries(call.query ?? {})) {
|
|
175
|
-
if (value
|
|
176
|
-
|
|
185
|
+
if (value != null) {
|
|
186
|
+
query += `${query ? "&" : "?"}${key}=${segment(value)}`;
|
|
187
|
+
}
|
|
177
188
|
}
|
|
178
|
-
const query = params.toString();
|
|
179
189
|
const base = /^https?:/.test(call.path) ? "" : this.config.baseUrl;
|
|
180
|
-
return
|
|
190
|
+
return base + call.path + query;
|
|
181
191
|
}
|
|
182
192
|
/**
|
|
183
193
|
* One send and its body read, under this attempt's timer — which stops
|
|
184
194
|
* at the headers of a stream the caller reads (and of nothing else).
|
|
185
195
|
*/
|
|
186
196
|
async attempt(url, init, read, options) {
|
|
197
|
+
// The caller's signal joined to the timer by hand, its reason carried
|
|
198
|
+
// over: `AbortSignal.any` is not React Native's. Left joined for a
|
|
199
|
+
// stream, whose body the caller's abort still ends.
|
|
200
|
+
const { signal } = options;
|
|
187
201
|
const timer = new AbortController();
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
const stop = () => timer.abort(signal?.reason);
|
|
203
|
+
const clock = setTimeout(stop, options.timeoutMs ?? this.config.timeoutMs);
|
|
204
|
+
signal?.addEventListener("abort", stop);
|
|
205
|
+
// Already aborted: no event will come, and `fetch` sends nothing.
|
|
206
|
+
if (signal?.aborted)
|
|
207
|
+
stop();
|
|
192
208
|
try {
|
|
193
|
-
const response = await this.config.fetch(url, {
|
|
209
|
+
const response = await this.config.fetch(url, {
|
|
210
|
+
...init,
|
|
211
|
+
signal: timer.signal,
|
|
212
|
+
});
|
|
194
213
|
const body = !response.ok
|
|
195
214
|
? await response.text()
|
|
196
215
|
: read === "stream"
|
|
@@ -202,14 +221,16 @@ export class Transport {
|
|
|
202
221
|
}
|
|
203
222
|
catch (error) {
|
|
204
223
|
// The caller's abort is the caller's, whatever else happened.
|
|
205
|
-
if (
|
|
206
|
-
throw
|
|
224
|
+
if (signal?.aborted)
|
|
225
|
+
throw signal.reason;
|
|
207
226
|
if (timer.signal.aborted)
|
|
208
227
|
return { kind: "timeout" };
|
|
209
228
|
return { kind: "network", error };
|
|
210
229
|
}
|
|
211
230
|
finally {
|
|
212
231
|
clearTimeout(clock);
|
|
232
|
+
if (read !== "stream")
|
|
233
|
+
signal?.removeEventListener("abort", stop);
|
|
213
234
|
}
|
|
214
235
|
}
|
|
215
236
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumavox/server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "The Lumavox API from your server: end users, ephemeral keys and the rest, typed from the published OpenAPI document. Zero dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|