stratus-db 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -51,7 +51,8 @@ of the intended type.
51
51
  ## API
52
52
 
53
53
  All methods return promises; failures reject with `StratusError` (`status`, `error` - e.g.
54
- `"invalid-query"`, `"conflict"` -, `message`). 429s are retried with backoff (`retries`).
54
+ `"invalid-query"`, `"conflict"` -, `message`). 429s are retried with backoff (`retries`), and so is API
55
+ Gateway's 403 Forbidden for a brand-new key, which works within a minute or two (`newKeyWaitMs`, 120000).
55
56
 
56
57
  - Catalog: `listDbs({deleted})`, `dbByName(name)`, `dbRecord(id)`, `createDb({name, primaryKey})`,
57
58
  `renameDb(id, name)`, `deleteDb(id)`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stratus-db",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "The stratus-db service from JavaScript: typed JSON over fetch, no dependencies.",
5
5
  "homepage": "https://stratus-db.com",
6
6
  "license": "Apache-2.0",
package/stratus-db.js CHANGED
@@ -18,7 +18,7 @@
18
18
  // against a local service, opts.caller names the caller (x-stratus-caller).
19
19
 
20
20
  /** The client's version: package.json's, which release-js.yml checks against it. */
21
- export const VERSION = "0.2.0";
21
+ export const VERSION = "0.2.1";
22
22
 
23
23
  const USER_AGENT = `stratus-db-js/${VERSION}`;
24
24
  const IN_NODE = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
@@ -537,6 +537,8 @@ export function connect(opts) {
537
537
  return new Client(opts);
538
538
  }
539
539
 
540
+ const GATEWAY_FORBIDDEN = /^\s*\{\s*"message"\s*:\s*"Forbidden"\s*\}\s*$/;
541
+
540
542
  export class Client {
541
543
  /**
542
544
  * @param {object} opts
@@ -545,6 +547,8 @@ export class Client {
545
547
  * @param {"hosted"|"function-urls"} [opts.addressing]
546
548
  * @param {string} [opts.caller] x-stratus-caller, for a local service
547
549
  * @param {number} [opts.retries] attempts after a 429 (default 10)
550
+ * @param {number} [opts.newKeyWaitMs] how long to keep retrying API Gateway's own 403 Forbidden, which a
551
+ * brand-new key gets for its first minute or so (default 120000; 0: never)
548
552
  * @param {number} [opts.resolveMs] function-urls: how long a database's URL is cached (5 minutes)
549
553
  * @param {typeof fetch} [opts.fetch] default globalThis.fetch
550
554
  */
@@ -556,6 +560,7 @@ export class Client {
556
560
  this.apiKey = opts.apiKey;
557
561
  this.caller = opts.caller;
558
562
  this.retries = opts.retries ?? 10;
563
+ this.newKeyWaitMs = opts.newKeyWaitMs ?? 120000;
559
564
  this.resolveMs = opts.resolveMs ?? 300000;
560
565
  this.fetch = opts.fetch ?? globalThis.fetch;
561
566
  this.urls = { at: 0, byId: new Map() };
@@ -570,7 +575,8 @@ export class Client {
570
575
  if (body !== undefined) headers["content-type"] = "application/json";
571
576
  if (this.apiKey) headers.authorization = `Bearer ${this.apiKey}`;
572
577
  if (this.caller) headers["x-stratus-caller"] = this.caller;
573
- for (let attempt = 0; ; attempt++) {
578
+ const started = Date.now();
579
+ for (let attempt = 0, forbidden = 0; ; attempt++) {
574
580
  const r = await this.fetch(url, { method, headers, body: body === undefined ? undefined : encode(body) });
575
581
  const textBody = await r.text();
576
582
  if (r.status === 429 && attempt < this.retries) {
@@ -578,6 +584,12 @@ export class Client {
578
584
  await sleep(Math.random() * Math.min(5000, 50 * 2 ** attempt));
579
585
  continue;
580
586
  }
587
+ // API Gateway's own refusal of a key it hasn't learned yet (a new key, its first minute or so):
588
+ // {"message":"Forbidden"} - the service's own errors always carry "error", and aren't retried
589
+ if (r.status === 403 && GATEWAY_FORBIDDEN.test(textBody) && Date.now() - started < this.newKeyWaitMs) {
590
+ await sleep(Math.min(15000, 1000 * 2 ** forbidden++));
591
+ continue;
592
+ }
581
593
  const ct = r.headers.get("content-type") ?? "";
582
594
  let value = textBody;
583
595
  if (ct.startsWith("application/json") && textBody !== "") {