dsh-unlag 1.9.37 → 1.9.39
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/index.js +37 -7
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -106,6 +106,18 @@ const MAX_TOTAL_SCRIPT_BYTES = 32 * 1024 * 1024
|
|
|
106
106
|
* is work the request itself must not be able to grow without bound.
|
|
107
107
|
*/
|
|
108
108
|
const MAX_BUNDLE_IDS = 128
|
|
109
|
+
/**
|
|
110
|
+
* How long one stale-bundle answer may spend before it gives the request back to the app's own
|
|
111
|
+
* route. Each row is bounded on its own, but a request can name many rows and the route answers
|
|
112
|
+
* whatever a socket asks: without a whole-request bound one made-up address holds a connection for
|
|
113
|
+
* as long as its rows add up.
|
|
114
|
+
*/
|
|
115
|
+
const MAX_ANSWER_MS = 15_000
|
|
116
|
+
/** The same bound for a test that must reach it quickly. */
|
|
117
|
+
function answerBudgetMs() {
|
|
118
|
+
const set = Number(process.env.DSH_UNLAG_STALE_MS ?? '')
|
|
119
|
+
return set > 0 ? set : MAX_ANSWER_MS
|
|
120
|
+
}
|
|
109
121
|
/**
|
|
110
122
|
* How long one startup check may spend reading the list. Each address is bounded on its own, but a
|
|
111
123
|
* list of them is not: dozens of slow addresses would otherwise hold a press past the moment the
|
|
@@ -180,6 +192,16 @@ function settingsOf(config) {
|
|
|
180
192
|
}
|
|
181
193
|
}
|
|
182
194
|
|
|
195
|
+
/**
|
|
196
|
+
* One log line, whatever the message. An address, a plugin id or an error can carry a line break,
|
|
197
|
+
* and a forged line in a file that is read as evidence is worse than a missing one; a message can
|
|
198
|
+
* also be arbitrarily long, and the log is one line per event.
|
|
199
|
+
*/
|
|
200
|
+
export function oneLine(value, limit = 2000) {
|
|
201
|
+
const text = String(value ?? '').replace(/[\r\n\u2028\u2029]+/gu, ' ')
|
|
202
|
+
return text.length > limit ? `${text.slice(0, limit)}\u2026` : text
|
|
203
|
+
}
|
|
204
|
+
|
|
183
205
|
function log(line) {
|
|
184
206
|
try {
|
|
185
207
|
const target = logTarget()
|
|
@@ -193,7 +215,7 @@ function log(line) {
|
|
|
193
215
|
/* a missing or unreadable log is created by the append below */
|
|
194
216
|
}
|
|
195
217
|
}
|
|
196
|
-
appendFileSync(target, `${new Date().toISOString()} ${line}\n`)
|
|
218
|
+
appendFileSync(target, `${new Date().toISOString()} ${oneLine(line)}\n`)
|
|
197
219
|
} catch {}
|
|
198
220
|
}
|
|
199
221
|
|
|
@@ -974,10 +996,11 @@ async function currentEntryBody(clientModules, graph, id) {
|
|
|
974
996
|
* answering with them in the requested order registers exactly the plugins the document expects,
|
|
975
997
|
* so a boot that would otherwise 404 and end the app comes up instead.
|
|
976
998
|
*/
|
|
977
|
-
async function regroupedBundleAnswer(clientModules, graph, request) {
|
|
999
|
+
async function regroupedBundleAnswer(clientModules, graph, request, deadline = Date.now() + answerBudgetMs()) {
|
|
978
1000
|
if (request.sourceMap) return null
|
|
979
1001
|
const parts = []
|
|
980
1002
|
for (const id of request.ids) {
|
|
1003
|
+
if (Date.now() > deadline) return null
|
|
981
1004
|
const body = await currentEntryBody(clientModules, graph, id)
|
|
982
1005
|
if (body === null) return null
|
|
983
1006
|
parts.push(body)
|
|
@@ -1005,7 +1028,7 @@ async function regroupedBundleAnswer(clientModules, graph, request) {
|
|
|
1005
1028
|
* booting from an old startup list that meets one ends the whole app: a boot that comes up without
|
|
1006
1029
|
* a plugin it no longer has beats that.
|
|
1007
1030
|
*/
|
|
1008
|
-
async function survivingBundleAnswer(clientModules, graph, request) {
|
|
1031
|
+
async function survivingBundleAnswer(clientModules, graph, request, deadline = Date.now() + answerBudgetMs()) {
|
|
1009
1032
|
if (request.sourceMap) return null
|
|
1010
1033
|
const entries = Array.isArray(graph?.entries) ? graph.entries : []
|
|
1011
1034
|
const served = new Set(entries.filter((row) => typeof row?.id === 'string').map((row) => row.id))
|
|
@@ -1013,6 +1036,7 @@ async function survivingBundleAnswer(clientModules, graph, request) {
|
|
|
1013
1036
|
if (gone.length === 0) return null
|
|
1014
1037
|
const parts = []
|
|
1015
1038
|
for (const id of request.ids) {
|
|
1039
|
+
if (Date.now() > deadline) return null
|
|
1016
1040
|
if (served.has(id)) {
|
|
1017
1041
|
const body = await currentEntryBody(clientModules, graph, id)
|
|
1018
1042
|
if (body === null) return null
|
|
@@ -1031,7 +1055,7 @@ async function survivingBundleAnswer(clientModules, graph, request) {
|
|
|
1031
1055
|
* that really contains every plugin it claims is handed back; anything unsure answers null, so the
|
|
1032
1056
|
* route then behaves exactly as before and an unknown id still ends as a plain 404.
|
|
1033
1057
|
*/
|
|
1034
|
-
async function currentBundleAnswer(clientModules, method, url) {
|
|
1058
|
+
async function currentBundleAnswer(clientModules, method, url, deadline = Date.now() + answerBudgetMs()) {
|
|
1035
1059
|
const request = bundleRequest(url)
|
|
1036
1060
|
if (request === null) return null
|
|
1037
1061
|
const graph = clientModules.graph()
|
|
@@ -1057,7 +1081,7 @@ async function currentBundleAnswer(clientModules, method, url) {
|
|
|
1057
1081
|
}
|
|
1058
1082
|
}
|
|
1059
1083
|
}
|
|
1060
|
-
return (await regroupedBundleAnswer(clientModules, graph, request)) ?? survivingBundleAnswer(clientModules, graph, request)
|
|
1084
|
+
return (await regroupedBundleAnswer(clientModules, graph, request, deadline)) ?? survivingBundleAnswer(clientModules, graph, request, deadline)
|
|
1061
1085
|
}
|
|
1062
1086
|
|
|
1063
1087
|
/** Answer one stale request over the plain request/response route an app registers. */
|
|
@@ -1355,7 +1379,10 @@ function releaseBundleShim(shim) {
|
|
|
1355
1379
|
}
|
|
1356
1380
|
// The mark belongs to this wrapper only; a mark left by a newer one stays on the route.
|
|
1357
1381
|
if (shim.route[SHIM_MARK] === shim.wrapped) delete shim.route[SHIM_MARK]
|
|
1358
|
-
} catch {
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
// A route left wrapped here would answer pages from a context that is going away.
|
|
1384
|
+
log(`cannot unwrap the bundle route: ${oneLine(error?.message ?? error)}`)
|
|
1385
|
+
}
|
|
1359
1386
|
}
|
|
1360
1387
|
|
|
1361
1388
|
/**
|
|
@@ -1558,7 +1585,10 @@ export async function readStartupScript(clientModules, url) {
|
|
|
1558
1585
|
try {
|
|
1559
1586
|
const answer = await currentBundleAnswer(clientModules, 'GET', url)
|
|
1560
1587
|
if (answer !== null) return { status: 200, arrayBuffer: async () => answer.body }
|
|
1561
|
-
} catch {
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
// The fallback is still the right answer; the failure itself must not vanish.
|
|
1590
|
+
log(`cannot answer ${oneLine(url)} at the revision the app serves now: ${oneLine(error?.message ?? error)}; asking the app's own route`)
|
|
1591
|
+
}
|
|
1562
1592
|
return fetchBundleAt(clientModules, url, 'GET')
|
|
1563
1593
|
}
|
|
1564
1594
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-unlag",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A press applies plugin edits right away and brings a laggy interface back; automatic reloading stays off unless you turn it on, so the press is the only trigger, and nothing restarts.",
|
|
6
6
|
"license": "MIT",
|