@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 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
- super(init.message, init.cause === undefined ? {} : { cause: init.cause });
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;
@@ -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
- /** A request id of the API's own shape: 16 hex. */
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
- /** A request id of the API's own shape: 16 hex. */
39
- export const newRequestId = () => crypto.randomUUID().replaceAll("-", "").slice(0, 16);
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
- const once = call.raw instanceof ReadableStream;
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
- const params = new URLSearchParams();
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 !== undefined && value !== null)
176
- params.set(key, String(value));
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 `${base}${call.path}${query ? `?${query}` : ""}`;
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 clock = setTimeout(() => timer.abort(), options.timeoutMs ?? this.config.timeoutMs);
189
- const signal = options.signal
190
- ? AbortSignal.any([options.signal, timer.signal])
191
- : timer.signal;
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, { ...init, signal });
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 (options.signal?.aborted)
206
- throw options.signal.reason;
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.1",
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",