Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
root: true,
env: {
browser: true,
commonjs: true,
Expand Down Expand Up @@ -27,5 +28,29 @@
'build/',
'coverage/',
'*.min.js'
],
// Type-aware rules to catch a forgotten `await`. They need TypeScript type info, so
// they run via @typescript-eslint with parserOptions.project, scoped to the source
// files in tsconfig.eslint.json. Test files and other excluded paths keep the plain
// parser so linting never errors on a file outside the TS program.
overrides: [
{
files: ['**/*.js'],
excludedFiles: ['**/*.test.js', 'tests/**', 'static/**', 'coverage/**'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
project: './tsconfig.eslint.json'
},
plugins: ['@typescript-eslint'],
rules: {
// A promise-returning call left unhandled is almost always a forgotten await.
'@typescript-eslint/no-floating-promises': 'error',
// A promise used where a sync value is expected (e.g. `if (asyncThing())`).
// Warn, not error: there are intentional fire-and-forget callbacks.
'@typescript-eslint/no-misused-promises': 'warn'
}
}
]
};
};
2 changes: 1 addition & 1 deletion npmprojector/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ class PackageWatcher {
*/
stop() {
if (this.watcher) {
this.watcher.close();
this.watcher.close().catch((err) => this.log.error('Error closing watcher: ' + err.message));
this.watcher = null;
}
if (this.debounceTimer) {
Expand Down
2 changes: 1 addition & 1 deletion packages/packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@
pckLog.info('Starting initial package crawler...');

// Run crawler in background (non-blocking)
setImmediate(async () => {

Check warning on line 957 in packages/packages.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
try {
await this.runCrawler();
pckLog.info('Initial package crawler completed successfully');
Expand Down Expand Up @@ -1712,7 +1712,7 @@
// Update download counts after successful response
// Do this asynchronously to not delay the response
setImmediate(() => {
this.incrementDownloadCounts(packageData.PackageVersionKey, id);
void this.incrementDownloadCounts(packageData.PackageVersionKey, id); // fire-and-forget; errors handled internally
});

} catch (error) {
Expand Down
16 changes: 8 additions & 8 deletions publisher/publisher.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@

this.stats.addTask('Publisher', Utilities.formatDuration(pollInterval)); // or however you want to display the frequency

this.taskProcessor = setInterval(async () => {

Check warning on line 319 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
if (this.shutdownRequested) return;

if (this.isProcessing) {
Expand Down Expand Up @@ -664,11 +664,11 @@
let err = '';
npm.stdout.on('data', () => { /* ignore */ });
npm.stderr.on('data', (d) => { err += d.toString(); });
npm.on('error', async (e) => {

Check warning on line 667 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
await this.logTaskMessage(taskId, 'warn', 'Could not run npm to update SUSHI: ' + e.message);
resolve();
});
npm.on('close', async (code) => {

Check warning on line 671 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
if (code === 0) {
let version = '';
try {
Expand Down Expand Up @@ -708,7 +708,7 @@
stderr += data.toString();
});

git.on('close', async (code) => {

Check warning on line 711 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
if (code === 0) {
await this.logTaskMessage(task.id, 'info', 'Repository cloned successfully');
resolve();
Expand All @@ -719,7 +719,7 @@
}
});

git.on('error', async (error) => {

Check warning on line 722 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
await this.logTaskMessage(task.id, 'error', 'Git clone error: ' + error.message);
reject(error);
});
Expand Down Expand Up @@ -763,7 +763,7 @@
// Heartbeat: emit a status line every 60s regardless of stdout activity,
// so silent phases of the Publisher (e.g. "Validating Resources") still
// surface a signal-of-life in the task log.
const heartbeat = setInterval(async () => {

Check warning on line 766 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
const elapsedMs = Date.now() - buildStart;
const sinceDataMs = Date.now() - lastDataAt;
let logKb = 0;
Expand All @@ -779,7 +779,7 @@
);
}, 60 * 1000);

java.on('close', async (code) => {

Check warning on line 782 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
logStream.end();

if (code === 0) {
Expand All @@ -792,7 +792,7 @@
}
});

java.on('error', async (error) => {

Check warning on line 795 in publisher/publisher.js

View workflow job for this annotation

GitHub Actions / Code Quality

Promise returned in function argument where a void return was expected
logStream.end();
await this.logTaskMessage(taskId, 'error', 'IG Publisher error: ' + error.message);
reject(error);
Expand Down Expand Up @@ -2665,14 +2665,14 @@
return colors[status] || 'secondary';
}

async logUserAction(userId, action, targetId, ipAddress) {
return new Promise((resolve) => {
this.db.run(
'INSERT INTO user_actions (user_id, action, target_id, ip_address) VALUES (?, ?, ?, ?)',
[userId, action, targetId, ipAddress],
() => resolve() // Don't fail if logging fails
);
});
// Fire-and-forget audit logging: callers don't await this, and it must never fail a
// request, so it returns void and swallows any DB error in the callback.
logUserAction(userId, action, targetId, ipAddress) {
this.db.run(
'INSERT INTO user_actions (user_id, action, target_id, ip_address) VALUES (?, ?, ?, ?)',
[userId, action, targetId, ipAddress],
() => {} // don't fail (or block) the request if audit logging fails
);
}

getStatus() {
Expand Down
4 changes: 2 additions & 2 deletions registry/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ class RegistryModule {

// Run initial crawl after a short delay
setTimeout(() => {
this.performCrawl();
this.performCrawl().catch((err) => this.logger.error('Registry crawl failed: ' + err.message));
}, 5000);

// Set up periodic crawling
this.stats.addTask("TxRegistry", `${intervalMinutes} min`);
this.crawlInterval = setInterval(() => {
this.performCrawl();
this.performCrawl().catch((err) => this.logger.error('Registry crawl failed: ' + err.message));
}, intervalMs);

this.logger.info(`Started periodic crawl every ${intervalMinutes} minutes`);
Expand Down
5 changes: 4 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -860,4 +860,7 @@ async function serveFhirsmithHome(req, res) {
}

// Start the server
startServer();
startServer().catch((err) => {
serverLog.error('Fatal error during startup:', err);
process.exit(1);
});
2 changes: 1 addition & 1 deletion token/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ class TokenModule {
}

if (userId) {
this.logSecurityEvent(userId, 'logout', req.ip, req.get('User-Agent'), {});
void this.logSecurityEvent(userId, 'logout', req.ip, req.get('User-Agent'), {}); // fire-and-forget audit; errors handled internally
}

res.redirect('/token/login');
Expand Down
9 changes: 9 additions & 0 deletions tsconfig.eslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"allowJs": true, "checkJs": false, "noEmit": true,
"moduleResolution": "node", "target": "es2022", "module": "commonjs",
"strict": false, "skipLibCheck": true
},
"include": ["**/*.js"],
"exclude": ["node_modules", "static", "**/*.test.js", "tests", "coverage"]
}
6 changes: 5 additions & 1 deletion tx/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,11 @@ class Library {
throw new Error("Unable to load VSAC provider unless vsacCfg is provided in the configuration");
}
let vsac = new VSACValueSetProvider(this.vsacCfg, this.stats);
vsac.initialize();
// Intentionally not awaited: initialize() might run a full VSAC sync on first load,
// which can take minutes - we don't want to block startup. But attach a handler
// so a failed background sync surfaces as a log error rather than an unhandled
// promise rejection.
vsac.initialize().catch((err) => this.log.error(`VSAC initialization failed: ${err.message}`));
this.valueSetProviders.push(vsac);
this.externalSources.push(vsac);
//const mem = process.memoryUsage();
Expand Down
16 changes: 8 additions & 8 deletions tx/library/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ class Renderer {
async renderMetadataTable(res, tbl, sourcePackage) {
this.renderMetadataVersion(res, tbl);
await this.renderMetadataProfiles(res, tbl);
this.renderMetadataTags(res, tbl);
this.renderMetadataLabels(res, tbl);
await this.renderMetadataTags(res, tbl);
await this.renderMetadataLabels(res, tbl);
this.renderMetadataLastUpdated(res, tbl);
this.renderMetadataSource(res, tbl);
this.renderProperty(tbl, 'TEST_PLAN_LANG', res.language);
Expand Down Expand Up @@ -221,32 +221,32 @@ class Renderer {
}
}

renderMetadataTags(res, tbl) {
async renderMetadataTags(res, tbl) {
if (res.meta?.tag) {
let tr = tbl.tr();
tr.td().b().tx(this.translate('GENERAL_PROF'));
if (res.meta.tag.length > 1) {
let ul = tr.td();
for (let u of res.meta.tag) {
this.renderCoding(ul.li(), u);
await this.renderCoding(ul.li(), u);
}
} else {
this.renderCoding(tr.td(), res.meta.tag[0]);
await this.renderCoding(tr.td(), res.meta.tag[0]);
}
}
}

renderMetadataLabels(res, tbl) {
async renderMetadataLabels(res, tbl) {
if (res.meta?.label) {
let tr = tbl.tr();
tr.td().b().tx(this.translate('GENERAL_PROF'));
if (res.meta.label.length > 1) {
let ul = tr.td();
for (let u of res.meta.label) {
this.renderCodin(ul.li(), u);
await this.renderCoding(ul.li(), u);
}
} else {
this.renderCoding(tr.td(), res.meta.label[0]);
await this.renderCoding(tr.td(), res.meta.label[0]);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tx/workers/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ class ValueSetChecker {
}
}
} else {
this.checkCanonicalStatusCS(path, op, prov, this.valueSet);
await this.checkCanonicalStatusCS(path, op, prov, this.valueSet);
let ctxt = await prov.locate(c.code);
if (!ctxt.context) {
// message can never be populated in pascal?
Expand Down
6 changes: 3 additions & 3 deletions xig/xig.js
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,7 @@ async function fetchResourceRows(queryParams, offset = 0, limit = 200) {
}

function rowToObject(row, queryParams) {
const { ver, realm, auth, type, rt } = queryParams;
const { type } = queryParams;
const packageObj = getPackage(row.PackageKey);

const obj = {
Expand Down Expand Up @@ -3156,7 +3156,7 @@ async function initializeXigModule(stats, xigConfig) {
if (!fs.existsSync(XIG_DB_PATH)) {
xigLog.info('No existing XIG database found, triggering initial download');
setTimeout(() => {
updateXigDatabase();
updateXigDatabase().catch((err) => xigLog.error('XIG database update failed: ' + err.message));
}, 5000);
}

Expand All @@ -3167,7 +3167,7 @@ async function initializeXigModule(stats, xigConfig) {
// Note: This assumes we're called only when XIG is enabled
if (xigConfig?.autoUpdate !== false) {
cron.schedule('0 2 * * *', () => {
updateXigDatabase();
updateXigDatabase().catch((err) => xigLog.error('XIG database update failed: ' + err.message));
});
}

Expand Down
Loading