@antelopejs/dms-frontend 0.7.0 → 0.7.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/dist/help.js
CHANGED
|
@@ -63,7 +63,7 @@ exports.ENVIRONMENT_VARIABLES = [
|
|
|
63
63
|
],
|
|
64
64
|
[
|
|
65
65
|
"DMS_BACKEND_WAKE_BUDGET_MS",
|
|
66
|
-
"How long a GET or HEAD waits out a backend's
|
|
66
|
+
"How long a GET or HEAD waits out a waking or starting backend's 503, in ms (default: 30000; 0: never)",
|
|
67
67
|
],
|
|
68
68
|
["DMS_CLIENT_BASE_URL", "Public frontend URL used in links and e-mails"],
|
|
69
69
|
["NO_COLOR", "Same as --no-color"],
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// allows.
|
|
5
5
|
|
|
6
6
|
import { GENERIC_ERROR_MESSAGE } from "./auth/backend.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { isWaitable } from "./backend-wake.mjs";
|
|
8
8
|
import { isFrontendVisit, redirectFrontendVisit } from "./inertia.mjs";
|
|
9
9
|
|
|
10
10
|
// What a JSON caller gets when the backend fails: the generic i18n key of the
|
|
@@ -47,7 +47,7 @@ async function refusalCode(response) {
|
|
|
47
47
|
|
|
48
48
|
/** The error for a failed backend response, with its typed code if any. */
|
|
49
49
|
export async function backendResponseError(response) {
|
|
50
|
-
const retryAfter =
|
|
50
|
+
const retryAfter = isWaitable(response)
|
|
51
51
|
? (response.headers.get("retry-after") ?? undefined)
|
|
52
52
|
: undefined;
|
|
53
53
|
return new BackendResponseError(
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// Waiting out a backend
|
|
1
|
+
// Waiting out a backend that is waking from sleep or still starting.
|
|
2
2
|
//
|
|
3
3
|
// A platform that scales the backend to zero answers the first requests with
|
|
4
4
|
// a 503 while it starts a replica, marked with the cold-start header and a
|
|
5
|
-
// Retry-After in whole seconds
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// Retry-After in whole seconds; a DMS backend that listens before it is ready
|
|
6
|
+
// answers 503 with a Retry-After too, without the header. A 503 with a
|
|
7
|
+
// Retry-After in whole seconds is the signal, with or without the header; a
|
|
8
|
+
// 503 without one goes out as it came. Only a GET or a HEAD is repeated; a
|
|
9
|
+
// POST, PATCH or DELETE is sent once.
|
|
10
10
|
//
|
|
11
|
-
// The wait starts with the first
|
|
11
|
+
// The wait starts with the first waitable answer and lasts at most the
|
|
12
12
|
// budget, the repeated requests included: one still waiting for its answer
|
|
13
13
|
// when the budget runs out is aborted, and no request starts after it. It
|
|
14
14
|
// ends at once when the client leaves.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
20
20
|
|
|
21
|
-
/** Header
|
|
21
|
+
/** Header a platform marks the 503 of a backend it is waking with. */
|
|
22
22
|
export const COLD_START_HEADER = "x-antelope-cold-start";
|
|
23
23
|
const COLD_START_VALUE = "1";
|
|
24
24
|
const UNAVAILABLE = 503;
|
|
@@ -34,12 +34,14 @@ const MS_PER_SECOND = 1_000;
|
|
|
34
34
|
const WHOLE_SECONDS = /^\d+$/;
|
|
35
35
|
const WHOLE_MILLISECONDS = /^\d+$/;
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Whether `response` is a 503 asking to come back after a Retry-After in
|
|
39
|
+
* whole seconds: a platform waking the backend (with the cold-start header)
|
|
40
|
+
* or a backend still starting.
|
|
41
|
+
*/
|
|
42
|
+
export function isWaitable(response) {
|
|
39
43
|
return (
|
|
40
|
-
response.status === UNAVAILABLE &&
|
|
41
|
-
response.headers.get(COLD_START_HEADER) === COLD_START_VALUE &&
|
|
42
|
-
WHOLE_SECONDS.test(retryAfter(response))
|
|
44
|
+
response.status === UNAVAILABLE && WHOLE_SECONDS.test(retryAfter(response))
|
|
43
45
|
);
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -57,7 +59,7 @@ export function wakeBudgetMs(env = process.env) {
|
|
|
57
59
|
return WHOLE_MILLISECONDS.test(raw) ? Number(raw) : DEFAULT_BUDGET_MS;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
/** The pause a
|
|
62
|
+
/** The pause a waitable answer's Retry-After asks for, kept between bounds. */
|
|
61
63
|
function retryDelayMs(response) {
|
|
62
64
|
return Math.min(
|
|
63
65
|
MAX_DELAY_MS,
|
|
@@ -66,17 +68,15 @@ function retryDelayMs(response) {
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
|
-
* What the client gets once the budget is spent: the last
|
|
70
|
-
* status and
|
|
71
|
+
* What the client gets once the budget is spent: the last waitable answer's
|
|
72
|
+
* status, Retry-After and cold-start header if it had one, without the body
|
|
73
|
+
* its pause already discarded.
|
|
71
74
|
*/
|
|
72
75
|
function expired(cold) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
headers
|
|
76
|
-
|
|
77
|
-
"retry-after": retryAfter(cold),
|
|
78
|
-
},
|
|
79
|
-
});
|
|
76
|
+
const headers = { "retry-after": retryAfter(cold) };
|
|
77
|
+
if (cold.headers.get(COLD_START_HEADER) === COLD_START_VALUE)
|
|
78
|
+
headers[COLD_START_HEADER] = COLD_START_VALUE;
|
|
79
|
+
return new Response(null, { status: UNAVAILABLE, headers });
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -102,9 +102,9 @@ async function repeat(url, init, deadline, now) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* `fetch`, waiting out
|
|
105
|
+
* `fetch`, waiting out waitable 503s to a GET or a HEAD until `budgetMs`
|
|
106
106
|
* from the first of them is spent or MAX_REPEATS repeats are sent. Then the
|
|
107
|
-
* caller gets
|
|
107
|
+
* caller gets the 503 with its `Retry-After` to relay. The client
|
|
108
108
|
* leaving, through `init.signal`, ends the wait with its AbortError.
|
|
109
109
|
*/
|
|
110
110
|
export async function fetchWaking(
|
|
@@ -116,7 +116,7 @@ export async function fetchWaking(
|
|
|
116
116
|
let response = await fetch(url, init);
|
|
117
117
|
if (!RETRYABLE_METHODS.has(method)) return response;
|
|
118
118
|
const deadline = now() + budgetMs;
|
|
119
|
-
for (let repeats = 0;
|
|
119
|
+
for (let repeats = 0; isWaitable(response); repeats += 1) {
|
|
120
120
|
const cold = response;
|
|
121
121
|
const delay = retryDelayMs(cold);
|
|
122
122
|
if (repeats >= MAX_REPEATS || now() + delay > deadline) return cold;
|