fhirsmith 0.18.1 → 1.0.0
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/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +190 -9
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +331 -33
- package/ips/jurisdictions.js +65 -0
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +131 -17
- package/ips/presenter.js +1 -1
- package/ips/profile-editor.js +283 -4
- package/ips/readme.md +170 -20
- package/ips/render.js +769 -109
- package/ips/scripts.js +46 -0
- package/ips/store.js +123 -11
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/api.js +179 -1
- package/registry/crawler.js +86 -108
- package/registry/documentation/api.md +68 -2
- package/registry/documentation/configuration.md +34 -0
- package/registry/documentation/internals.md +4 -1
- package/registry/documentation/readme.md +3 -2
- package/registry/model.js +8 -0
- package/registry/openapi.yaml +84 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +58 -6
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +158 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +50 -16
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +199 -24
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +193 -67
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +861 -95
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +125 -61
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/tho-precedence.js +40 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +314 -67
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/documentation.md +45 -21
- package/tx/sct/ecl.js +81 -17
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +175 -15
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +44 -17
- package/tx/vs/vs-vsac.js +649 -101
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/library/daily-report.js
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
// The report is:
|
|
13
13
|
// - a paragraph on the server's own stability since the last report: how much of the
|
|
14
14
|
// time it was up, restarts, and any upgrade (from the run history ServerStats keeps)
|
|
15
|
+
// - under the launcher (cluster.js), a paragraph on the cluster: processes that exited or
|
|
16
|
+
// were recycled and how long they were unavailable, and what the launcher refused or
|
|
17
|
+
// abandoned itself (library/cluster-events.js)
|
|
15
18
|
// - a paragraph from each running module that has something to say. A module takes part
|
|
16
19
|
// by having `dailyReport({since, now})` return markdown (a string, or a promise of one);
|
|
17
20
|
// an empty return means no paragraph. By default modules have nothing to say
|
|
@@ -94,6 +97,11 @@ function describeSpan(ms) {
|
|
|
94
97
|
return plural(Math.round(hours / 24), 'day');
|
|
95
98
|
}
|
|
96
99
|
|
|
100
|
+
/** "once", "twice", "3 times" */
|
|
101
|
+
function times(n) {
|
|
102
|
+
return n === 1 ? 'once' : n === 2 ? 'twice' : `${n} times`;
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
function plural(n, word) {
|
|
98
106
|
return `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
99
107
|
}
|
|
@@ -112,9 +120,10 @@ class DailyReport {
|
|
|
112
120
|
|
|
113
121
|
/**
|
|
114
122
|
* @param config - the "dailyReport" block
|
|
115
|
-
* @param deps - {log, stats, modules, mailer, npmAudit, version, serverId, stateFile}
|
|
123
|
+
* @param deps - {log, stats, modules, mailer, npmAudit, version, serverId, stateFile, cluster}
|
|
116
124
|
* modules is the server's {name: instance} map; npmAudit the server's NpmAudit (optional);
|
|
117
|
-
* version the server's version; serverId the name the report goes out under (config.hostName)
|
|
125
|
+
* version the server's version; serverId the name the report goes out under (config.hostName);
|
|
126
|
+
* cluster true for the master under the launcher (the cluster paragraph is always there)
|
|
118
127
|
*/
|
|
119
128
|
constructor(config, deps) {
|
|
120
129
|
this.config = config || {};
|
|
@@ -125,6 +134,7 @@ class DailyReport {
|
|
|
125
134
|
this.npmAudit = deps.npmAudit || null;
|
|
126
135
|
this.version = deps.version;
|
|
127
136
|
this.serverId = deps.serverId || os.hostname();
|
|
137
|
+
this.cluster = Boolean(deps.cluster);
|
|
128
138
|
this.stateFile = deps.stateFile || folders.filePath(STATE_FILE);
|
|
129
139
|
this.timer = null;
|
|
130
140
|
this.running = false;
|
|
@@ -255,6 +265,11 @@ class DailyReport {
|
|
|
255
265
|
parts.push(`# ${this.serverId} daily report for ${localDateTime(now)} covering the last ${describeSpan(now - since)}`);
|
|
256
266
|
parts.push('## Stability');
|
|
257
267
|
parts.push(this.stabilityParagraph(since, now));
|
|
268
|
+
const cluster = this.clusterParagraph(since, now);
|
|
269
|
+
if (cluster) {
|
|
270
|
+
parts.push('## Cluster');
|
|
271
|
+
parts.push(cluster);
|
|
272
|
+
}
|
|
258
273
|
for (const [name, module] of Object.entries(this.modules)) {
|
|
259
274
|
const text = await this.moduleParagraph(name, module, since, now);
|
|
260
275
|
if (text) {
|
|
@@ -333,6 +348,88 @@ class DailyReport {
|
|
|
333
348
|
return sentences.join(' ');
|
|
334
349
|
}
|
|
335
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Under the launcher: what it saw (library/cluster-events.js) - processes that exited or
|
|
353
|
+
* were recycled, how long each was unavailable, and what it answered itself. Null for a
|
|
354
|
+
* single server (no role, and no events recorded).
|
|
355
|
+
*/
|
|
356
|
+
clusterParagraph(since, now) {
|
|
357
|
+
const events = this.stats && typeof this.stats.clusterEventsSince === 'function' ? this.stats.clusterEventsSince(since, now) : null;
|
|
358
|
+
if (!this.cluster && !(events && events.length > 0)) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
if (!events) {
|
|
362
|
+
return 'No record of the launcher\'s events is available (the statistics database is not in use).';
|
|
363
|
+
}
|
|
364
|
+
const sum = (rows) => rows.reduce((n, r) => n + r.count, 0);
|
|
365
|
+
const of = (kind, process) => events.filter(e => e.kind === kind && (process === undefined || e.process === process));
|
|
366
|
+
const sentences = [];
|
|
367
|
+
const started = of('launcher-started');
|
|
368
|
+
if (started.length > 0) {
|
|
369
|
+
sentences.push(`The launcher started ${times(started.length)} (${started.map(e => localDateTime(e.time)).join(', ')}).`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const names = [...new Set(events.filter(e => e.process).map(e => e.process))]
|
|
373
|
+
.sort((a, b) => (a === 'master' ? -1 : b === 'master' ? 1 : a.localeCompare(b)));
|
|
374
|
+
const lines = [];
|
|
375
|
+
for (const name of names) {
|
|
376
|
+
const said = [];
|
|
377
|
+
const exited = of('exited', name);
|
|
378
|
+
if (exited.length > 0) {
|
|
379
|
+
const how = new Map();
|
|
380
|
+
for (const e of exited) {
|
|
381
|
+
const d = e.detail == null ? 'unknown' : /^-?\d+$/.test(e.detail) ? `exit code ${e.detail}` : e.detail;
|
|
382
|
+
how.set(d, (how.get(d) || 0) + 1);
|
|
383
|
+
}
|
|
384
|
+
said.push(`exited ${times(exited.length)} (${[...how].map(([d, n]) => n > 1 ? `${n} × ${d}` : d).join(', ')})`);
|
|
385
|
+
}
|
|
386
|
+
const recycled = of('recycled', name);
|
|
387
|
+
if (recycled.length > 0) {
|
|
388
|
+
said.push(`was recycled ${times(recycled.length)} (the last because ${recycled[recycled.length - 1].detail || 'of its memory'})`);
|
|
389
|
+
}
|
|
390
|
+
const down = of('ready', name).reduce((n, e) => n + e.ms, 0);
|
|
391
|
+
if (down > 0) {
|
|
392
|
+
said.push(`was unavailable for ${formatSpan(down)} in all`);
|
|
393
|
+
}
|
|
394
|
+
const timedOut = sum(of('upstream-timeout', name));
|
|
395
|
+
if (timedOut > 0) {
|
|
396
|
+
said.push(`did not begin ${plural(timedOut, 'response')} in time (504)`);
|
|
397
|
+
}
|
|
398
|
+
const failed = sum(of('upstream-failed', name));
|
|
399
|
+
if (failed > 0) {
|
|
400
|
+
said.push(`failed to answer ${plural(failed, 'request')} (502)`);
|
|
401
|
+
}
|
|
402
|
+
const lost = sum(of('lost-to-restart', name));
|
|
403
|
+
if (lost > 0) {
|
|
404
|
+
said.push(`was restarting when ${plural(lost, 'request')} came for a cache-id it had issued`);
|
|
405
|
+
}
|
|
406
|
+
if (said.length > 0) {
|
|
407
|
+
lines.push(`- ${name}: ${said.join('; ')}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (of('exited').length === 0 && of('recycled').length === 0) {
|
|
411
|
+
sentences.push('No process exited or was recycled.');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const refused = [
|
|
415
|
+
[sum(of('refused-busy')), 'because no process was free in time'],
|
|
416
|
+
[sum(of('refused-not-ready')), 'because no process was ready'],
|
|
417
|
+
[sum(of('refused-stopping')), 'while it was stopping']
|
|
418
|
+
].filter(([n]) => n > 0);
|
|
419
|
+
if (refused.length > 0) {
|
|
420
|
+
const total = refused.reduce((n, [c]) => n + c, 0);
|
|
421
|
+
sentences.push(`The launcher refused ${plural(total, 'request')} itself (503): ${refused.map(([n, why]) => `${n} ${why}`).join(', ')}.`);
|
|
422
|
+
} else {
|
|
423
|
+
sentences.push('The launcher refused no requests itself.');
|
|
424
|
+
}
|
|
425
|
+
const cut = sum(of('write-timeout'));
|
|
426
|
+
if (cut > 0) {
|
|
427
|
+
sentences.push(`It closed ${plural(cut, 'connection')} whose client took none of a response in time (writeTimeoutSeconds).`);
|
|
428
|
+
}
|
|
429
|
+
const text = sentences.join(' ');
|
|
430
|
+
return lines.length > 0 ? `${text}\n\n${lines.join('\n')}` : text;
|
|
431
|
+
}
|
|
432
|
+
|
|
336
433
|
/**
|
|
337
434
|
* A module's paragraph, if it has one. A module that throws gets a line
|
|
338
435
|
* saying so rather than taking the report down.
|
|
@@ -430,3 +527,4 @@ module.exports.describeSpan = describeSpan;
|
|
|
430
527
|
// for modules writing their paragraph: "3 packages", "1 package"
|
|
431
528
|
module.exports.plural = plural;
|
|
432
529
|
module.exports.localDateTime = localDateTime;
|
|
530
|
+
module.exports.formatBytes = formatBytes;
|
package/library/html-server.js
CHANGED
|
@@ -12,11 +12,24 @@ const packageJson = require('../package.json');
|
|
|
12
12
|
|
|
13
13
|
let sponsorMessage = '';
|
|
14
14
|
|
|
15
|
+
// The accessibility check panel (config accessibility.checkPanel): a button at the bottom of
|
|
16
|
+
// every page that runs axe-core (WCAG 2.0 A/AA) and FHIRsmith's own structure checks on the page
|
|
17
|
+
// as the browser has it. The scripts are in static/a11y/; a11y-check.js only loads axe-core when
|
|
18
|
+
// the button is pressed.
|
|
19
|
+
const A11Y_PANEL = `
|
|
20
|
+
<div id="fhir-a11y-panel" data-axe="/a11y/axe.min.js" lang="en" style="clear: both; margin: 20px 10px; padding: 8px; border: 1px solid #888888; background-color: #f4f4f4; color: #000000; font-size: 13px; font-family: sans-serif">
|
|
21
|
+
<p style="margin: 0"><b>Accessibility check</b> (checks this page as it is now against WCAG 2.0 A/AA, plus heading structure, reported by HHS 508 checklist item) <button type="button" class="fhir-a11y-run">Check this page</button> <span class="fhir-a11y-status" role="status"></span></p>
|
|
22
|
+
<div class="fhir-a11y-results"></div>
|
|
23
|
+
</div>
|
|
24
|
+
<script type="text/javascript" src="/a11y/a11y-check.js"></script>
|
|
25
|
+
`;
|
|
26
|
+
|
|
15
27
|
class HtmlServer {
|
|
16
28
|
log;
|
|
17
29
|
|
|
18
30
|
constructor() {
|
|
19
31
|
this.templates = new Map(); // templateName -> template content
|
|
32
|
+
this.accessibilityPanel = false;
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
useLog(logv) {
|
|
@@ -27,6 +40,17 @@ class HtmlServer {
|
|
|
27
40
|
sponsorMessage = msg;
|
|
28
41
|
}
|
|
29
42
|
|
|
43
|
+
setAccessibilityPanel(enabled) {
|
|
44
|
+
this.accessibilityPanel = !!enabled;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// put the accessibility check panel at the end of the page body (or the end of the page, if
|
|
48
|
+
// there's no </body>)
|
|
49
|
+
addAccessibilityPanel(html) {
|
|
50
|
+
const i = html.toLowerCase().lastIndexOf('</body>');
|
|
51
|
+
return i < 0 ? html + A11Y_PANEL : html.slice(0, i) + A11Y_PANEL + html.slice(i);
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
// Template Management
|
|
31
55
|
loadTemplate(templateName, templatePath) {
|
|
32
56
|
try {
|
|
@@ -92,6 +116,8 @@ class HtmlServer {
|
|
|
92
116
|
['library-link', () => renderOptions.libraryLink || ''],
|
|
93
117
|
// raw HTML too: the OpenAPI description, where a template's module has one in some places only
|
|
94
118
|
['api-link', () => renderOptions.apiLink || ''],
|
|
119
|
+
// raw HTML: the packages module's feed check, in the nav bar only when it's enabled
|
|
120
|
+
['feed-check-link', () => renderOptions.feedCheckLink || ''],
|
|
95
121
|
// raw HTML: a link to the current project (or other context) in the nav bar, where the
|
|
96
122
|
// module has one
|
|
97
123
|
['context-link', () => renderOptions.contextLink || ''],
|
|
@@ -108,7 +134,7 @@ class HtmlServer {
|
|
|
108
134
|
// a placeholder that isn't known is left as it is, as before
|
|
109
135
|
const html = template.replace(/\[%([^%\]\s]{1,64})%\]/g, (m, name) => (values.has(name) ? values.get(name)() : m));
|
|
110
136
|
|
|
111
|
-
return html;
|
|
137
|
+
return this.accessibilityPanel ? this.addAccessibilityPanel(html) : html;
|
|
112
138
|
}
|
|
113
139
|
|
|
114
140
|
// Express Response Helper
|
package/library/i18nsupport.js
CHANGED
|
@@ -254,6 +254,28 @@ class I18nSupport {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
let english = null;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* A message from translations/Messages.properties, in English, with its parameters, as
|
|
261
|
+
* I18nSupport.translate gives it: for code with no request languages to hand (the shared
|
|
262
|
+
* cache store's lazy reads, the launcher). The message id goes with it, so a client (or a
|
|
263
|
+
* caller that has the languages: msgParams) can render it in another language.
|
|
264
|
+
* @param {string} messageId
|
|
265
|
+
* @param {Array} [parameters]
|
|
266
|
+
*/
|
|
267
|
+
function englishMessage(messageId, parameters = []) {
|
|
268
|
+
if (!english) {
|
|
269
|
+
english = getProperties(fs.readFileSync(path.join(__dirname, '..', 'translations', 'Messages.properties'), 'utf8'));
|
|
270
|
+
}
|
|
271
|
+
const message = english[messageId];
|
|
272
|
+
if (!message) {
|
|
273
|
+
return messageId;
|
|
274
|
+
}
|
|
275
|
+
return I18nSupport.prototype._substituteParameters(message.trim(), parameters).replaceAll("''", "'");
|
|
276
|
+
}
|
|
277
|
+
|
|
257
278
|
module.exports = {
|
|
258
|
-
I18nSupport
|
|
279
|
+
I18nSupport,
|
|
280
|
+
englishMessage
|
|
259
281
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Keep-alive timeouts for the HTTP server behind a reverse proxy.
|
|
2
|
+
//
|
|
3
|
+
// nginx keeps idle upstream connections open for 60 s (keepalive_timeout in an upstream block),
|
|
4
|
+
// while Node closes an idle one after 5 s. When nginx reuses a connection at the moment Node
|
|
5
|
+
// closes it, the request fails with a reset, and nginx does not retry a POST: the client gets a
|
|
6
|
+
// 502. Node must therefore keep idle connections open longer than the proxy does, and
|
|
7
|
+
// headersTimeout must be longer still, or Node applies it to the idle connection first.
|
|
8
|
+
|
|
9
|
+
const KEEP_ALIVE_TIMEOUT_MS = 65000;
|
|
10
|
+
const HEADERS_TIMEOUT_MS = 66000;
|
|
11
|
+
|
|
12
|
+
function applyKeepAliveTimeouts(server) {
|
|
13
|
+
server.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MS;
|
|
14
|
+
server.headersTimeout = HEADERS_TIMEOUT_MS;
|
|
15
|
+
return server;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { applyKeepAliveTimeouts, KEEP_ALIVE_TIMEOUT_MS, HEADERS_TIMEOUT_MS };
|
package/library/languages.js
CHANGED
|
@@ -583,6 +583,12 @@ class Languages {
|
|
|
583
583
|
* Global language definitions loaded from IETF registry
|
|
584
584
|
*/
|
|
585
585
|
class LanguageDefinitions {
|
|
586
|
+
// Private use tags of any length are valid, so the cache of parsed tags is capped
|
|
587
|
+
// with a simple LRU (as RegExUtilities does), or unique tags would grow it forever.
|
|
588
|
+
// Default for languageTagCacheSize: an 8 KB tag takes about 46 KB parsed, so 1,000
|
|
589
|
+
// such tags are at most about 46 MB; real requests use far fewer distinct tags.
|
|
590
|
+
static MAX_PARSED = 1000;
|
|
591
|
+
|
|
586
592
|
constructor() {
|
|
587
593
|
this.languages = new Map();
|
|
588
594
|
this.extLanguages = new Map();
|
|
@@ -596,6 +602,7 @@ class LanguageDefinitions {
|
|
|
596
602
|
// case-insensitive, and the registry writes these in mixed case.
|
|
597
603
|
this.wholeTags = new Map();
|
|
598
604
|
this.parsed = new Map(); // Cache for parsed languages
|
|
605
|
+
this.maxParsed = LanguageDefinitions.MAX_PARSED; // Infinity = no limit
|
|
599
606
|
}
|
|
600
607
|
|
|
601
608
|
/**
|
|
@@ -949,13 +956,20 @@ class LanguageDefinitions {
|
|
|
949
956
|
// The cache key carries the options: the same tag can be valid under one set of
|
|
950
957
|
// rules and not another, so a lenient parse must not be served to a strict caller.
|
|
951
958
|
const key = (options.caseInsensitive ? 'i' : '') + (options.checkCombinations ? 'c' : '') + ':' + code;
|
|
952
|
-
|
|
953
|
-
|
|
959
|
+
const cached = this.parsed.get(key);
|
|
960
|
+
if (cached) {
|
|
961
|
+
// Move to most-recently-used position by re-inserting.
|
|
962
|
+
this.parsed.delete(key);
|
|
963
|
+
this.parsed.set(key, cached);
|
|
964
|
+
return cached;
|
|
954
965
|
}
|
|
955
966
|
|
|
956
967
|
try {
|
|
957
968
|
const lang = new Language(code, this, options);
|
|
958
|
-
|
|
969
|
+
if (this.parsed.size >= this.maxParsed) {
|
|
970
|
+
// Evict the oldest entry: Map iteration order is insertion order.
|
|
971
|
+
this.parsed.delete(this.parsed.keys().next().value);
|
|
972
|
+
}
|
|
959
973
|
this.parsed.set(key, lang);
|
|
960
974
|
return lang;
|
|
961
975
|
} catch (e) {
|
package/library/logger.js
CHANGED
|
@@ -299,6 +299,37 @@ class Logger {
|
|
|
299
299
|
};
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
// Apply config.logging to a logger that is already running (the server starts logging with the
|
|
303
|
+
// defaults before config.json is read). Same options as the constructor; anything not given keeps
|
|
304
|
+
// its default
|
|
305
|
+
configure(options = {}) {
|
|
306
|
+
this._flushSync();
|
|
307
|
+
this.level = options.level || DEFAULTS.level;
|
|
308
|
+
this.maxFiles = options.maxFiles ?? DEFAULTS.maxFiles;
|
|
309
|
+
this.maxSize = Logger._parseSize(options.maxSize) || DEFAULTS.maxSize;
|
|
310
|
+
this.showConsole = options.console ?? DEFAULTS.console;
|
|
311
|
+
this.consoleErrors = options.consoleErrors ?? DEFAULTS.consoleErrors;
|
|
312
|
+
this._flushSize = options.flushSize ?? DEFAULTS.flushSize;
|
|
313
|
+
|
|
314
|
+
clearInterval(this._flushTimer);
|
|
315
|
+
this._flushTimer = setInterval(() => this._flush(), options.flushInterval ?? DEFAULTS.flushInterval);
|
|
316
|
+
if (this._flushTimer.unref) this._flushTimer.unref();
|
|
317
|
+
|
|
318
|
+
if (options.logDir && path.resolve(options.logDir) !== path.resolve(this.logDir)) {
|
|
319
|
+
if (this._fd !== null) {
|
|
320
|
+
try { fs.closeSync(this._fd); } catch (_) { /* intentional */ }
|
|
321
|
+
}
|
|
322
|
+
this._fd = null;
|
|
323
|
+
this._currentDate = null;
|
|
324
|
+
this._currentFileSize = 0;
|
|
325
|
+
this.logDir = options.logDir;
|
|
326
|
+
if (!fs.existsSync(this.logDir)) {
|
|
327
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
this.info('Logger configured @ ' + this.logDir + ', level ' + this.level, {});
|
|
331
|
+
}
|
|
332
|
+
|
|
302
333
|
setLevel(level) {
|
|
303
334
|
this.level = level;
|
|
304
335
|
this.info(`Log level changed to ${level}`);
|